有没有办法取消升压信号2中的信号传播?

时间:2011-12-07 23:53:37

标签: c++ exception boost signals boost-signals2

我想使用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,如果类型为stopPropagation(),则会在event上调用"goat"

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;
}

如果你有提升(我使用1.44)并且你想编译它,完整的代码和Makefile是https://gist.github.com/1445230

1 个答案:

答案 0 :(得分:2)

您可以使用自定义信号合并器执行此操作。这些在boost.signals教程的“信号返回值(高级)”部分进行了解释: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()的输入迭代器引用信号的槽的集合。每次取消引用其中一个迭代器时,它都会调用插槽。因此,您可以让其中一个插槽返回一个值,以指示它不希望调用任何其他插槽。

然后你只需将它传递给信号的第二个模板arg:

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

现在你可以实现thingHappened来返回一个bool值,表明你是否想要停止信号。