强大的C ++事件模式

时间:2016-03-07 15:37:16

标签: c++ c++11 events design-patterns

我已经整理了一个简单的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); 
}

问题是开发人员需要手动删除每个订阅者,否则事件的fire()在迭代通过每个订阅者的所有订阅者时将最终触发可能或可能的对象的方法还没有存在。

以下是完整的代码以及一个工作示例: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;
}

2 个答案:

答案 0 :(得分:1)

如果不是因为没有&#34;知道&#34;它们的存在,你可以简单地将你想要的副作用编码到一个Listener虚拟基础析构函数中,以便在它离开作用域时取消注册。

回调API绝对是一个类似C&#34;的构造。要桥接到C ++,您需要提供实例上下文以及回调方法。发送器API仅将void * opaque客户端上下文引用作为参数传递,因此它实际上并不知道&#34;知道&#34;或者关心客户端类型,它只需要传回注册时给出的相同的void * _t。 这使得main()能够注册&amp; listener1&#34;这个&#34;指针作为参考。

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

答案 1 :(得分:1)

我在这里描述了我的解决方案: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;
}