2011-03-05 16 views
13

Sadece yapabileceğim bir C# olayını C++ 'da uygulamak istedim. Ben C++ olayları uygulamak mümkün mü?

nasıl bu durumda int func(float) yılında, T ne varsa olmak () operatörünü aşırı do ... Ben alt yanlış olduğunu biliyorum ama benim en büyük sorunun ne olduğunu biliyoruz, saplanıp? Yapamam Yapabilirmiyim? İyi bir alternatif uygulayabilir miyim?

#include <deque> 
using namespace std; 

typedef int(*MyFunc)(float); 

template<class T> 
class MyEvent 
{ 
    deque<T> ls; 
public: 
    MyEvent& operator +=(T t) 
    { 
     ls.push_back(t); 
     return *this; 
    } 
}; 
static int test(float f){return (int)f; } 
int main(){ 
    MyEvent<MyFunc> e; 
    e += test; 
} 
+0

'aşırı etmeyin operatörü + =', bunu son derece kafa karıştırıcı buluyorum . – fredoverflow

+2

Bkz http://www.codeproject.com/KB/cpp/ImpossiblyFastCppDelegate.aspx sorunun –

+1

biri C++ doğrudan C# stil olayların merkezi parçası olan delege, desteklemediği olmasıdır. http://www.codeproject.com/KB/cpp/FastDelegate.aspx Delegeleri standart olmayan, ancak çoğu derleyici tarafından desteklenen bir şekilde uygular. – CodesInChaos

cevap

22

Kuvvetlendirme kullanabiliyorsa, sinyaller-yuvaları/olayları/gözlemciler işlevsellik sağlar Boost.Signals2 kullanmayı düşünün. Bu basit ve kullanımı kolaydır ve oldukça esnektir. Boost.Signals2 ayrıca, keyfi olarak yüklenebilen nesneleri (örneğin, funktörler veya ciltli üye işlevleri gibi) kaydetmenize izin verir, böylece daha esnektir ve nesne yaşamlarını doğru şekilde yönetmenize yardımcı olacak birçok işleve sahiptir.


Eğer bunu kendiniz yapmaya çalışıyorsanız, doğru yoldasınız demektir. Yine de bir probleminiz var: Tam olarak, kayıtlı fonksiyonların her birinden dönen değerler ile ne yapmak istersiniz? Yalnızca bir değeri operator()'dan döndürebilirsiniz, böylece hiçbir şey mi yoksa sonuçlardan mi yoksa sonuçların bir şekilde mi toplanacağına karar vermelisiniz.

Sonuçları göz ardı etmek istediğimiz varsayarsak, bunu uygulamak oldukça basittir, ancak her bir parametre türünü ayrı bir şablon türü parametresi olarak alırsanız biraz daha kolay olur (alternatif olarak, Boost.TypeTraits gibi bir şey kullanabilirsiniz), hangi kolayca) bir işlev türünü incelemek sağlar:

template <typename TArg0> 
class MyEvent 
{ 
    typedef void(*FuncPtr)(TArg0); 
    typedef std::deque<FuncPtr> FuncPtrSeq; 

    FuncPtrSeq ls; 
public: 
    MyEvent& operator +=(FuncPtr f) 
    { 
     ls.push_back(f); 
     return *this; 
    } 

    void operator()(TArg0 x) 
    { 
     for (typename FuncPtrSeq::iterator it(ls.begin()); it != ls.end(); ++it) 
      (*it)(x); 
    } 
}; 

Bu void dönüş türü kayıtlı işlevi gerektirir. Herhangi bir geri dönüş türü ile işlevlerini kabul edebilmek için, FuncPtr

typedef std::function<void(TArg0)> FuncPtr; 

olmak değiştirmek (veya C++ 0x sürümü kullanılabilir yoksa boost::function veya std::tr1::function tuşlarını kullanarak). Dönüş değerleri ile bir şeyler yapmak istiyorsanız, döndürme türünü başka bir şablon parametresi olarak MyEvent'a alabilirsiniz. Bu yapmak için nispeten basit olmalıdır.

void test(float) { } 

int main() 
{ 
    MyEvent<float> e; 
    e += test; 
    e(42); 
} 

Eğer olayların farklı arities desteklemek sağlayan bir diğer yaklaşım, fonksiyon tipi için tek bir tip parametre kullanmak olacaktır ve:

yukarıdaki uygulamayla

aşağıdaki çalışması gerekir Her biri farklı sayıda argüman alan aşırı yüklenmiş operator() aşırı yük var. Bu aşırı yükler şablonlar olmalı, aksi takdirde olayın gerçek aritesine uymayan aşırı yüklenmeler için derleme hataları alırsınız. İşte işe yarar bir örnek:

template <typename TFunc> 
class MyEvent 
{ 
    typedef typename std::add_pointer<TFunc>::type FuncPtr; 
    typedef std::deque<FuncPtr> FuncPtrSeq; 

    FuncPtrSeq ls; 
public: 
    MyEvent& operator +=(FuncPtr f) 
    { 
     ls.push_back(f); 
     return *this; 
    } 

    template <typename TArg0> 
    void operator()(TArg0 a1) 
    { 
     for (typename FuncPtrSeq::iterator it(ls.begin()); it != ls.end(); ++it) 
      (*it)(a1); 
    } 

    template <typename TArg0, typename TArg1> 
    void operator()(const TArg0& a1, const TArg1& a2) 
    { 
     for (typename FuncPtrSeq::iterator it(ls.begin()); it != ls.end(); ++it) 
      (*it)(a1, a2); 
    } 
}; 

(burada C++ 0x dan std::add_pointer kullandım, ancak bu tür değiştirici ayrıca Boost ve C++ TR1 bulunabilir; bu sadece o işlevi kullanmak için biraz daha temiz hale getirir . mümkündür, ancak mevcut tasarımı ile

void test1(float) { } 
void test2(float, float) { } 

int main() 
{ 
    MyEvent<void(float)> e1; 

    e1 += test1; 
    e1(42); 

    MyEvent<void(float, float)> e2; 
    e2 += test2; 
    e2(42, 42); 
} 
+0

Bu, öğrenmenin * bu tür geri çağırma sistemini nasıl uygulayacağına dair bir soru olduğunu düşünüyorum. –

+0

-edit- sizi düzenlemedim. Şimdi okuuyorum. –

+0

Mükemmel cevap. Fonksiyonu kopyala/yapıştırmayı denedim ama 2 şablon kullanarak ve aynı ada sahip iki sınıfın farklı şablonlar kullanması gerçeğini beğenmedim. Bunun hakkında düşünüyordum, ancak şablonların miktarını önceden tanımlayarak MyEvent , MyEvent 'u kabul etmenin bir yolu yok mu? Şu andaki gibi MyEvent0-9'um var (9'dan fazla parametreye ihtiyacım yok ....... belki de bir boşluk yapmalıyım * listeyi de geçebilirim). MyEvent2 çözümünü kullanıyor ve MyEvent AND MyEvent 'u kullanmak imkansız mı? –

1

:; doğrudan bir işlev türü kullanabilirsiniz beri şablon bir işlev işaretçisi türü kullanmak gerekmez) İşte kullanım örnek. Sorun, geri çağırma işlevi imzasının şablon argümanınıza kilitlenmiş olması gerçeğidir.Bunu desteklemeye çalışman gerektiğini düşünmüyorum, aynı listedeki tüm geri bildirimler aynı imzana sahip olmalı, sence de öyle değil mi?

3

Kesinlikle yapabilirsiniz. James McNellis zaten eksiksiz bir çözüm bağladığı, ancak oyuncak örneğin biz aşağıdakileri yapabilirsiniz: keşfetmek için işlev işaretçisi türü bileşenlerini ayrı kızdırmak için kısmi uzmanlık

İşte
#include <deque> 
using namespace std; 

typedef int(*MyFunc)(float); 

template<typename F> 
class MyEvent; 

template<class R, class Arg> 
class MyEvent<R(*)(Arg)> 
{ 
    typedef R (*FuncType)(Arg); 
    deque<FuncType> ls; 
    public: 
    MyEvent<FuncType>& operator+=(FuncType t) 
    { 
      ls.push_back(t); 
      return *this; 
    } 

    void operator()(Arg arg) 
    { 
      typename deque<FuncType>::iterator i = ls.begin(); 
      typename deque<FuncType>::iterator e = ls.end(); 
      for(; i != e; ++i) { 
        (*i)(arg); 
      } 
    } 
}; 
static int test(float f){return (int)f; } 
int main(){ 
    MyEvent<MyFunc> e; 
    e += test; 
    e(2.0); 
} 

yaptığım kullanımını argüman türü. boost.signals bunu ve daha fazlasını yapar, tip silme gibi özellikleri kullanır ve işlevsiz işaretçi tarafından yazılabilir nesneler için bu bilgiyi belirlemek için özellikler.

N bağımsız değişkenler için iki yaklaşım vardır. "Kolay' yolu, yani Ancak, özellikler eklendiğini öncesinden bu yana bu işi yapıyorum. Variadic şablonlar ve birkaç diğer özellikleri yararlanarak edilir C++ 0x için eklenmiş ve herhangi eğer derleyicileri hangi bilmiyorum .

template<typename R, typename Arg0, typename Arg1> 
class MyEvent<R(*)(Arg0, Arg1)> 
{ 
    typedef R (*FuncType)(Arg0, Arg1); 
    deque<FuncType> ls; 
    ... 
    void operatror()(Arg0 a, Arg1) 
    { ... } 
    MyEvent<FuncType>& operator+=(FuncType f) 
    { ls.push_back(f); } 
    ... 
}; 

THis zaten bunu kontrol vurmuş boost.signals gibi kütüphaneler neden olduğu elbette sıkıcı alır (ve: Yani biz ise zor yoldan, yapabiliriz variadic şablonları desteklemek henüz yine uzman bu kullanım makroları, vb) bezginlik bazı rahatlatmak için.

aşağıdaki

gibi bir tekniği kullanabilirsiniz bir MyEvent<int, int> tarzı sözdizimi için izin vermek için
struct NullEvent; 

template<typename A = NullEvent, typename B = NullEvent, typename C = NullEvent> 
class HisEvent; 


template<> 
struct HisEvent<NullEvent,NullEvent,NullEvent> 
{ void operator()() {} }; 

template<typename A> 
struct HisEvent<A,NullEvent,NullEvent> 
{ void operator()(A a) {} }; 

template<typename A, typename B> 
struct HisEvent<A, B, NullEvent> 
{ 
    void operator()(A a, B b) {} 
}; 

template<typename A, typename B, typename C> 
struct HisEvent 
{ 
    void operator()(A a, B b, C c) 
    {} 
}; 

static int test(float f){return (int)f; } 
int main(){ 
    MyEvent<MyFunc> e; 
    e += test; 
    e(2.0); 

    HisEvent<int> h; 
    HisEvent<int, int> h2; 
} 

NullEvent tipi bir tutucu olarak kullanılan ve tekrar Arity anlamaya kısmi uzmanlık kullanımı.

+0

+1 etkin bir şekilde gönderim için aynı şeyi yanıtlamak için çalışıyordum. –

+0

Şimdiye kadar gördüğüm her şeyle ilgili sorun, argüman miktarını ve türünü belirtemiyor. Şu anda olduğu gibi, arg tek bir argüman olsa bile, ne istersem iki tane? Aslında bir fikrim var ama işe yarayacağını düşünmüyorum. –

+0

Mükemmel cevap.Fonksiyonu kopyala/yapıştırmayı denedim ama 2 şablon kullanarak ve aynı ada sahip iki sınıfın farklı şablonlar kullanması gerçeğini beğenmedim. Bunun hakkında düşünüyordum, ancak şablonların miktarını önceden tanımlayarak MyEvent , MyEvent 'u kabul etmenin bir yolu yok mu? Şu andaki gibi MyEvent0-9'um var (9'dan fazla parametreye ihtiyacım yok ....... belki de bir boşluk yapmalıyım * listeyi de geçebilirim). MyEvent2 çözümünü kullanıyor ve MyEvent AND MyEvent 'u kullanmak imkansız mı? –

1

DÜZENLEME: Eklenen parçacığı güvenli uygulama, this Yanıta göre. düzeltmeleri ve performans iyileştirmeleri

Bu ekleyerek James McNellis' birinin geliştirilmesinde benim versiyonudur Birçok: operator-=, variadic şablon, kolayca nesneleri ve örnek üye işlevlerini bağlamak için saklanan çağrılabilir nesneler, kolaylık Bind(func, object) ve Unbind(func, object) yöntemlerin herhangi ariety destekleyecek atama operatörleri ve nullptr ile karşılaştırma. Ben sadece benim girişimleri daha esnek std::function kullanmak std::add_pointer kullanarak taşındı (lambdas ve std :: fonksiyonu hem kabul eder). Ayrıca ben zaten yararlı/çok güvenli olması için görünmüyor çünkü daha hızlı yineleme ve operatörleri *this dönen kaldırılan için std::vector kullanmak taşındı. Yine C# semantik eksik: C# olaylar beyan edildiği sınıftan dışından temizlenemez (bir şablon haline tipine devlet dostlukla bu eklemek çok kolay olacaktır).

Bu geribildirim karşılama kod işlemi şu:

#pragma once 

#include <typeinfo> 
#include <functional> 
#include <stdexcept> 
#include <memory> 
#include <atomic> 
#include <cstring> 

template <typename TFunc> 
class Event; 

template <class RetType, class... Args> 
class Event<RetType(Args ...)> final 
{ 
private: 
    typedef typename std::function<RetType(Args ...)> Closure; 

    struct ComparableClosure 
    { 
     Closure Callable; 
     void *Object; 
     uint8_t *Functor; 
     int FunctorSize; 

     ComparableClosure(const ComparableClosure &) = delete; 

     ComparableClosure() : Object(nullptr), Functor(nullptr), FunctorSize(0) { } 

     ComparableClosure(Closure &&closure) : Callable(std::move(closure)), Object(nullptr), Functor(nullptr), FunctorSize(0) { } 

     ~ComparableClosure() 
     { 
      if (Functor != nullptr) 
       delete[] Functor; 
     } 

     ComparableClosure & operator=(const ComparableClosure &closure) 
     { 
      Callable = closure.Callable; 
      Object = closure.Object; 
      FunctorSize = closure.FunctorSize; 
      if (closure.FunctorSize == 0) 
      { 
       Functor = nullptr; 
      } 
      else 
      { 
       Functor = new uint8_t[closure.FunctorSize]; 
       std::memcpy(Functor, closure.Functor, closure.FunctorSize); 
      } 

      return *this; 
     } 

     bool operator==(const ComparableClosure &closure) 
     { 
      if (Object == nullptr && closure.Object == nullptr) 
      { 
       return Callable.target_type() == closure.Callable.target_type(); 
      } 
      else 
      { 
       return Object == closure.Object && FunctorSize == closure.FunctorSize 
        && std::memcmp(Functor, closure.Functor, FunctorSize) == 0; 
      } 
     } 
    }; 

    struct ClosureList 
    { 
     ComparableClosure *Closures; 
     int Count; 

     ClosureList(ComparableClosure *closures, int count) 
     { 
      Closures = closures; 
      Count = count; 
     } 

     ~ClosureList() 
     { 
      delete[] Closures; 
     } 
    }; 

    typedef std::shared_ptr<ClosureList> ClosureListPtr; 

private: 
    ClosureListPtr m_events; 

private: 
    bool addClosure(const ComparableClosure &closure) 
    { 
     auto events = std::atomic_load(&m_events); 
     int count; 
     ComparableClosure *closures; 
     if (events == nullptr) 
     { 
      count = 0; 
      closures = nullptr; 
     } 
     else 
     { 
      count = events->Count; 
      closures = events->Closures; 
     } 

     auto newCount = count + 1; 
     auto newClosures = new ComparableClosure[newCount]; 
     if (count != 0) 
     { 
      for (int i = 0; i < count; i++) 
       newClosures[i] = closures[i]; 
     } 

     newClosures[count] = closure; 
     auto newEvents = ClosureListPtr(new ClosureList(newClosures, newCount)); 
     if (std::atomic_compare_exchange_weak(&m_events, &events, newEvents)) 
      return true; 

     return false; 
    } 

    bool removeClosure(const ComparableClosure &closure) 
    { 
     auto events = std::atomic_load(&m_events); 
     if (events == nullptr) 
      return true; 

     int index = -1; 
     auto count = events->Count; 
     auto closures = events->Closures; 
     for (int i = 0; i < count; i++) 
     { 
      if (closures[i] == closure) 
      { 
       index = i; 
       break; 
      } 
     } 

     if (index == -1) 
      return true; 

     auto newCount = count - 1; 
     ClosureListPtr newEvents; 
     if (newCount == 0) 
     { 
      newEvents = nullptr; 
     } 
     else 
     { 
      auto newClosures = new ComparableClosure[newCount]; 
      for (int i = 0; i < index; i++) 
       newClosures[i] = closures[i]; 

      for (int i = index + 1; i < count; i++) 
       newClosures[i - 1] = closures[i]; 

      newEvents = ClosureListPtr(new ClosureList(newClosures, newCount)); 
     } 

     if (std::atomic_compare_exchange_weak(&m_events, &events, newEvents)) 
      return true; 

     return false; 
    } 

public: 
    Event() 
    { 
     std::atomic_store(&m_events, ClosureListPtr()); 
    } 

    Event(const Event &event) 
    { 
     std::atomic_store(&m_events, std::atomic_load(&event.m_events)); 
    } 

    ~Event() 
    { 
     (*this) = nullptr; 
    } 

    void operator =(const Event &event) 
    { 
     std::atomic_store(&m_events, std::atomic_load(&event.m_events)); 
    } 

    void operator=(nullptr_t nullpointer) 
    { 
     while (true) 
     { 
      auto events = std::atomic_load(&m_events); 
      if (!std::atomic_compare_exchange_weak(&m_events, &events, ClosureListPtr())) 
       continue; 

      break; 
     } 
    } 

    bool operator==(nullptr_t nullpointer) 
    { 
     auto events = std::atomic_load(&m_events); 
     return events == nullptr; 
    } 

    bool operator!=(nullptr_t nullpointer) 
    { 
     auto events = std::atomic_load(&m_events); 
     return events != nullptr; 
    } 

    void operator +=(Closure f) 
    { 
     ComparableClosure closure(std::move(f)); 
     while (true) 
     { 
      if (addClosure(closure)) 
       break; 
     } 
    } 

    void operator -=(Closure f) 
    { 
     ComparableClosure closure(std::move(f)); 
     while (true) 
     { 
      if (removeClosure(closure)) 
       break; 
     } 
    } 

    template <typename TObject> 
    void Bind(RetType(TObject::*function)(Args...), TObject *object) 
    { 
     ComparableClosure closure; 
     closure.Callable = [object, function](Args&&...args) 
     { 
      return (object->*function)(std::forward<Args>(args)...); 
     }; 
     closure.FunctorSize = sizeof(function); 
     closure.Functor = new uint8_t[closure.FunctorSize]; 
     std::memcpy(closure.Functor, (void*)&function, sizeof(function)); 
     closure.Object = object; 

     while (true) 
     { 
      if (addClosure(closure)) 
       break; 
     } 
    } 

    template <typename TObject> 
    void Unbind(RetType(TObject::*function)(Args...), TObject *object) 
    { 
     ComparableClosure closure; 
     closure.FunctorSize = sizeof(function); 
     closure.Functor = new uint8_t[closure.FunctorSize]; 
     std::memcpy(closure.Functor, (void*)&function, sizeof(function)); 
     closure.Object = object; 

     while (true) 
     { 
      if (removeClosure(closure)) 
       break; 
     } 
    } 

    void operator()() 
    { 
     auto events = std::atomic_load(&m_events); 
     if (events == nullptr) 
      return; 

     auto count = events->Count; 
     auto closures = events->Closures; 
     for (int i = 0; i < count; i++) 
      closures[i].Callable(); 
    } 

    template <typename TArg0, typename ...Args2> 
    void operator()(TArg0 a1, Args2... tail) 
    { 
     auto events = std::atomic_load(&m_events); 
     if (events == nullptr) 
      return; 

     auto count = events->Count; 
     auto closures = events->Closures; 
     for (int i = 0; i < count; i++) 
      closures[i].Callable(a1, tail...); 
    } 
}; 

Ben bu konuda o test: C# yaptığı gibi

#include <iostream> 
using namespace std; 

class Test 
{ 
public: 
    void foo() { cout << "Test::foo()" << endl; } 
    void foo1(int arg1, double arg2) { cout << "Test::foo1(" << arg1 << ", " << arg2 << ") " << endl; } 
}; 

class Test2 
{ 
public: 

    Event<void()> Event1; 
    Event<void(int, double)> Event2; 
    void foo() { cout << "Test2::foo()" << endl; } 
    Test2() 
    { 
     Event1.Bind(&Test2::foo, this); 
    } 
    void foo2() 
    { 
     Event1(); 
     Event2(1, 2.2); 
    } 
    ~Test2() 
    { 
     Event1.Unbind(&Test2::foo, this); 
    } 
}; 

int main(int argc, char* argv[]) 
{ 
    (void)argc; 
    (void)argv; 

    Test2 t2; 
    Test t1; 

    t2.Event1.Bind(&Test::foo, &t1); 
    t2.Event2 += [](int arg1, double arg2) { cout << "Lambda(" << arg1 << ", " << arg2 << ") " << endl; }; 
    t2.Event2.Bind(&Test::foo1, &t1); 
    t2.Event2.Unbind(&Test::foo1, &t1); 
    function<void(int, double)> stdfunction = [](int arg1, double arg2) { cout << "stdfunction(" << arg1 << ", " << arg2 << ") " << endl; }; 
    t2.Event2 += stdfunction; 
    t2.Event2 -= stdfunction; 
    t2.foo2(); 
    t2.Event2 = nullptr; 
} 
İlgili konular