任何方式来取消信号传播在升压信号s2没有例外

Any way to cancel signal propagation in boost signals2 without exceptions?

本文关键字:信号 s2 传播 何方 任何方 取消      更新时间:2023-10-16

我想使用boost::signals2来处理我的c++应用程序中的事件通知。我希望实现与浏览器DOM事件类似的功能,特别是能够停止事件的传播,以便当前接收器是最后一个知道信号的接收器,随后的接收器不被调用。(请参阅http://www.w3.org/TR/DOM-Level-3-Events/#events-event-type-stopImmediatePropagation了解更多浏览器中的工作原理)

我有一个假设的App类,其信号称为thingHappened。很可能只有一个App实例,还有其他几个不同类型的Widget类将connectthingHappened接收ThingEvent通知。有时,小部件想要使用(停止)ThingEvent,这样就不会通知其他Widget

起初我想知道我是否可以用shared_connection_block实现这一点,但现在我明白这一次只抑制一个连接。最初,我将shared_ptr<ThingEvent>传递给我的信号,但一旦信号被调用,就没有办法干预其传播。如果我传递一个shared_ptr,我可以让信号接收器检查事件的值并返回它是否设置,但我不想把这个细节推给我的库的用户。

我发现的解决方案是在堆栈上传递ThingEvent,以便为每个接收器复制它。如果我在事件上设置mStopPropagation,那么当它被销毁时,我可以抛出异常并且信号调用终止。这样做的缺点是,我需要在调用信号的地方使用自己的try/catch,从风格上讲,这意味着我使用exception是为了一个普通的目的。有没有更好的办法?

这是我假设的App类,带有信号thingHappened:

class App
{
public:
    boost::signals2::signal<void (class ThingEvent)> thingHappened;
};

我的ThingEvent类,带有一些关于事件的数据(例如类型)和一个mStopPropagation属性,如果在析构函数中设置它,将导致抛出异常:

class ThingEvent
{
public:
    ThingEvent(string type): mType(type), mStopPropagation(false) { }
    ~ThingEvent() 
    {
        if (mStopPropagation) {
            throw exception();
        }
    }
    void stopPropagation() { mStopPropagation = true; }
    string getType() { return mType; }
private:
    string mType;
    bool mStopPropagation;
};

这里有一个示例信号消费者,一个Widget,如果类型是"goat",它将在event上调用stopPropagation():

class Widget
{
public:
    Widget(string name): mName(name) {}
    ~Widget() {}
    void thingHappened(ThingEvent thing)
    { 
        cout << thing.getType() << " thingHappened in widget " << mName << endl; 
        if (thing.getType() == "goat") {
            thing.stopPropagation();
        }
    }
    string getName() 
    {
        return mName;
    }
private:
    string mName;
};

最后,这里是使用这些类的快速main()函数:

int main()
{
    App app;
    Widget w1("1");
    Widget w2("2");
    Widget w3("3");
    boost::signals2::connection c1 = app.thingHappened.connect(boost::bind(&Widget::thingHappened, &w1, _1));
    boost::signals2::connection c2 = app.thingHappened.connect(boost::bind(&Widget::thingHappened, &w2, _1));
    boost::signals2::connection c3 = app.thingHappened.connect(boost::bind(&Widget::thingHappened, &w3, _1));
    // all three widgets will receive this
    app.thingHappened(ThingEvent("otter"));
    {
        // suppress calls to c2
        boost::signals2::shared_connection_block block(c2,true);
        // only w1 and w3 will receive this
        app.thingHappened(ThingEvent("badger"));
    }
    // Widgets call ThingEvent::stopPropagation() if the type is "goat"
    try {
        // only w1 will receive this
        app.thingHappened(ThingEvent("goat"));
    } catch (exception &e) {
        // ThingEvent's destructor throws if mStopPropagation is true
        std::cout << "exception thrown by thingHappened(goat)" << std::endl;
    }
    return 0;
}

如果你手头有boost(我使用1.44),你想编译它,完整的代码和Makefile在https://gist.github.com/1445230

您可以使用自定义信号合并器来完成此操作。这些在boost的"信号返回值(高级)"一节中进行了解释。信号教程:http://www.boost.org/doc/libs/1_48_0/doc/html/signals/tutorial.html#id3070223

要点如下:

struct MyCombiner {
    typedef bool result_type;
    template <typename InputIterator> result_type operator()(InputIterator aFirstObserver, InputIterator aLastObserver) const {
        result_type val = false;
        for (; aFirstObserver != aLastObserver && !val; ++aFirstObserver)  {
            val = *aFirstObserver;            
        }
        return val;
    }
};

operator()的输入迭代器指向信号槽的集合。每次对其中一个迭代器解引用时,它都会调用槽。因此,您可以让其中一个插槽返回一个值,以表明它不希望调用任何其他插槽。

然后把它传递给你的信号的第二个模板参数:

boost::signals2::signal<bool(ThingEvent), MyCombiner> sig;

现在你可以实现thingHappened来返回一个bool值,指示你是否希望信号被停止