强大的C++事件模式

Robust C++ event pattern

本文关键字:事件 模式 C++      更新时间:2023-10-16

我整理了一个简单的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: 42n";
        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;
}

如果不是因为对象不"知道"它们的存在,您可以简单地将您想要的副作用编码到 Listener 虚拟基析构函数中,以便在它离开范围时取消注册自身。

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

将 Listener:

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

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