我想使用boost::signals2
来处理我的c++应用程序中的事件通知。我希望实现与浏览器DOM事件类似的功能,特别是能够停止事件的传播,以便当前接收器是最后一个知道信号的接收器,随后的接收器不被调用。(请参阅http://www.w3.org/TR/DOM-Level-3-Events/#events-event-type-stopImmediatePropagation了解更多浏览器中的工作原理)
我有一个假设的App
类,其信号称为thingHappened
。很可能只有一个App
实例,还有其他几个不同类型的Widget
类将connect
到thingHappened
接收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值,指示你是否希望信号被停止