2016-03-07 19 views
2

我已经把一个简单的C++事件模式,其允许执行以下操作:鲁棒C++事件模式

struct Emitter { 
    Event<float> ev;  
    void triggerEvent() { ev.fire(42.0); } 
}; 

struct Listener { 
    void gotEvent(float x) { ... } 
}; 

int main() { 
    // event source and listener unaware of each other's existence 
    Emitter emitter(); 
    Listener listener(); 

    // hook them up 
    emitterA.ev.addSubscriber(&listener, &Listener::gotEvent); 
    { 
     Listener listener2(); 
     emitter.ev.addSubscriber(&listener2, &Listener::gotEvent); 
     emitter.triggerEvent(); 
     emitter.ev.removeSubscriber(&listener2); 
     //^PROBLEM! 
    } 

    emitter.triggerEvent(); 

    emitter.ev.removeSubscriber(&listener1); 
} 

的问题是,该显影剂需要手动除去每个用户,否则事件的火()在遍历所有用户的迭代中,最终会触发一个对象的方法,这个对象可能存在也可能不存在。

下面是完整的代码,从一个工作示例在一起:http://coliru.stacked-crooked.com/a/8bb20dacf50bf073

我会在下面贴了后人。

如果我注释掉违规行99,它仍然有效!但这显然只是因为内存尚未被覆盖。

这是一个危险的错误,因为它可能处于休眠状态。

我怎样才能以这种方式进行编码,而不会让我接触到这个潜在的UB错误?

有什么办法我行35 ..

template<class... Args> 
class Event { 
    : 
    void fire(Args... args) { 
     for(auto& f : subscribers) 
      f->call(args...); 
如果每个用户仍然存在

能在某种程度上检测......

,同时仍保留的事实,发射器和用户不知道彼此的存在?

完整列表:

#include <vector> 
#include <iostream> 
#include <algorithm> 
#include <memory> 

using namespace std; 

template<class... Args> 
class SubscriberBase { 
public: 
    virtual void call(Args... args) = 0; 
    virtual bool instanceIs(void* t) = 0; 
    virtual ~SubscriberBase() { }; 
}; 

template<class T, class... Args> 
class Subscriber : public SubscriberBase<Args...> { 
private: 
    T* t; 
    void(T::*f)(Args...); 
public: 
    Subscriber(T* _t, void(T::*_f)(Args...)) : t(_t), f(_f) { } 
    void call(Args... args) final { (t->*f)(args...); } 
    bool instanceIs(void* _t) final { return _t == (void*)t; } 
    ~Subscriber()    final { cout << "~Subscriber() hit! \n"; } 
}; 

template<class... Args> 
class Event { 
private: 
    using SmartBasePointer = unique_ptr<SubscriberBase<Args...>>; 
    std::vector<SmartBasePointer> subscribers; 
public: 
    void fire(Args... args) { 
     for(auto& f : subscribers) 
      f->call(args...); 
    } 

    template<class T> 
    void addSubscriber(T* t, void(T::*f)(Args... args)) { 
     auto s = new Subscriber <T, Args...>(t, f); 
     subscribers.push_back(SmartBasePointer(s)); 
    } 

    template<class T> 
    void removeSubscriber(T* t) { 
     auto to_remove = std::remove_if(
      subscribers.begin(), 
      subscribers.end(), 
      [t](auto& s) { return s->instanceIs((void*)t); } 
      ); 
     subscribers.erase(to_remove, subscribers.end()); 
    } 
}; 

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 

// example usage: 
class Emitter { 
private: 
    string name; 
public:  
    Event<float> eventFloat; 
    Event<bool, int> eventB; 

    Emitter(string _name) : name(_name) { } 

    void triggerEvent() { 
     cout << name << "::triggerEvent() ~ Firing event with: 42\n"; 
     eventFloat.fire(42.0f); 
    } 
}; 

struct Listener { 
    string name; 
    Listener(string _name) 
     : name(_name) { 
     cout << name << "()\n"; 
    } 
    ~Listener() { 
     cout << "~" << name << "()\n"; 
     //emitter.eventFloat.removeSubscriber(this); 
    } 

    void gotEvent(float x) { cout << name <<"::gotEvent hit with value: " << x << endl; } 
}; 

int main() { 
    // event source and listener unaware of each other's existence 
    Emitter emitterA("emitterA"); 
    Listener listener1("listener1"); 

    // hook them up 
    emitterA.eventFloat.addSubscriber(&listener1, &Listener::gotEvent); 
    { 
     Listener listener2("listener2"); 
     emitterA.eventFloat.addSubscriber(&listener2, &Listener::gotEvent); 
     emitterA.triggerEvent(); 
     //emitterA.eventFloat.removeSubscriber(&listener2); // hmm this is awkward 
    } 

    emitterA.triggerEvent(); 

    emitterA.eventFloat.removeSubscriber(&listener1); 
    emitterA.triggerEvent(); 

    return 0; 
} 

回答

1

如果不是因为对象不“知道”他们的存在,你可以简单地影响到你想进入一个监听器虚拟基础析构函数离开时注销本身代码的侧范围。

回调API确实是一个“C”类构造。要桥接到C++,您需要提供实例上下文以及回调方法。 Emitter API将void * opaque客户端上下文引用仅作为参数传递,因此它实际上并不“知道”或关心客户端类型,只需要返回与注册时相同的void * _t。 这使得main()可以将& listener1“this”指针注册为引用。

将Listener :: getEvent()转换为“C”类型的静态方法,该方法接受一些void *指针,然后将其转换为Listener对象并在处理事件之前使用它来确定对象的存在。一个私有的,静态的std :: set容器可以方便地进行验证。这安全地完成了进入C++土地的桥梁。

0

我在这里描述我的解决办法:http://www.juce.com/forum/topic/signals-slots-juce#comment-321103

http://coliru.stacked-crooked.com/a/b2733e334f4a5289

#include <vector> 
#include <iostream> 
#include <algorithm> 
#include <memory> 
#include <string> 

using namespace std; 

// an event holds a vector of subscribers 
// when it fires, each is called 

template<class... Args> 
class SubscriberBase { 
public: 
    virtual void call(Args... args) = 0; 
    virtual bool instanceIs(void* t) = 0; 
    virtual ~SubscriberBase() { }; 
}; 

template<class T, class... Args> 
class Subscriber : public SubscriberBase<Args...> { 
private: 
    T* t; 
    void(T::*f)(Args...); 
public: 
    Subscriber(T* _t, void(T::*_f)(Args...)) : t(_t), f(_f) { } 
    void call(Args... args) final { (t->*f)(args...); } 
    bool instanceIs(void* _t) final { return _t == (void*)t; } 
    ~Subscriber()    final { cout << "~Subscriber() hit! \n"; } 
}; 

// our Listener will derive from EventListener<Listener> 
// which holds a list of a events it is subscribed to. 
// As these events will have different sigs, we need a base-class. 
// We will store pointers to this base-class. 
class EventBase { 
public: 
    virtual void removeSubscriber(void* t) = 0; 
}; 

template<class... Args> 
class Event : public EventBase { 
private: 
    using SmartBasePointer = unique_ptr<SubscriberBase<Args...>>; 
    std::vector<SmartBasePointer> subscribers; 
public: 
    void fire(Args... args) { 
     for (auto& f : subscribers) 
      f->call(args...); 
    } 

    template<class T> 
    void addSubscriber(T* t, void(T::*f)(Args... args)) { 
     auto s = new Subscriber <T, Args...>(t, f); 
     subscribers.push_back(SmartBasePointer(s)); 
    } 

    //template<class T> 
    void removeSubscriber(void* t) final { 
     auto to_remove = std::remove_if(
      subscribers.begin(), 
      subscribers.end(), 
      [t](auto& s) { return s->instanceIs(t); } 
     ); 
     subscribers.erase(to_remove, subscribers.end()); 
    } 
}; 

// derive your listener classes: struct MyListener : EventListener<MyListener>, i.e. CRTP 
template<class Derived> 
class EventListener { 
private: 
    // all events holding a subscription to us... 
    std::vector<EventBase*> events; 

public: 
    template<class... Args> 
    void connect(Event<Args...>& ev, void(Derived::*listenerMethod)(Args... args)) { 
     ev.addSubscriber((Derived*)this, listenerMethod); 
     events.push_back(&ev); 
    } 

    // ...when the listener dies, we must notify them all to remove subscription 
    ~EventListener() { 
     for (auto& e : events) 
      e->removeSubscriber((void*)this); 
    } 
}; 

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 

// example usage: 
class Publisher { 
private: 
    string name; 
public: 
    Event<float> eventFloat; 
    Event<bool, int> eventB; 

    Publisher(string _name) : name(_name) { } 

    void triggerEvent() { 
     cout << name << "::triggerEvent() ~ Firing event with: 42\n"; 
     eventFloat.fire(42.0f); 
    } 
}; 

struct Listener : EventListener<Listener> { 
    string name; 
    Listener(string _name) 
     : name(_name) { 
     cout << name << "()\n"; 
    } 
    ~Listener() { 
     cout << "~" << name << "()\n"; 
     //emitter.eventFloat.removeSubscriber(this); 
    } 

    void gotEvent(float x) { cout << name << "::gotEvent hit with value: " << x << endl; } 
}; 

int main() { 
    // event source and listener unaware of each other's existence 
    Publisher publisherA("publisherA"); 

    Listener listener1("listener1"); 
    listener1.connect(publisherA.eventFloat, &Listener::gotEvent); 

    { 
     Listener listener2("listener2"); 
     listener2.connect(publisherA.eventFloat, &Listener::gotEvent); 

     publisherA.triggerEvent(); 
    } 

    publisherA.triggerEvent(); 

    return 0; 
}