I'd like to use boost::signals2
to handle event notification in my C++ app. I'm hoping to implement something with similar functionality to browser DOM events, specifically the ability to stop the propagation of an event so that the current receiver is the last one to know about a signal and subsequent receivers are not called. (see http://www.w3.org/TR/DOM-Level-3-Events/#events-event-type-stopImmediatePropagation for more on how this works in browsers)
I have a hypothetical App
class with a signal called thingHappened
. It's likely there'll only be one App
instance, with several other Widget
classes of varying types that will connect
to thingHappened
to receive ThingEvent
notifications. Sometimes the widget would like to consume (stop) the ThingEvent
so that no other Widget
s are notified.
At first I wondered if I could achieve this with a shared_connection_block
but now I understand that this only suppresses one connection at a time. Initially I passed a shared_ptr<ThingEvent>
to my signal but once the signal was called there was no way to intervene with its propagation. If I pass a shared_ptr I could have signal receivers check a value on the event and return if it's set, but I don't want to push that detail off to users of my library.
The solution I've found is to pass a ThingEvent
on the stack so that it is copied for each receiver. If I set mStopPropagation
on the event then when it is destroyed I can throw an exception and the signal calls terminate. The downside to this is that I need my own try/catch at the point where the signal is called, and stylistically it means I'm using an exception
for an unexceptional purpose. Is there a better way?
Here's my hypothetical App
class, with a signal thingHappened
:
class App
{
public:
boost::signals2::signal<void (class ThingEvent)> thingHappened;
};
My ThingEvent
class, with some data about the event (e.g. type) and an mStopPropagation
property that will cause an exception to be thrown if it is set in the destructor:
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;
};
Here's a sample signal consumer, a Widget
, that will call stopPropagation()
on an event
if the type is "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;
};
Finally, here's a quick main()
function that uses these classes:
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;
}
If you have boost to hand (I'm using 1.44) and you want to compile it, the full code and Makefile are at https://gist.github.com/1445230
You can do this with a custom signal combiner. These are explained in the "Signal Return Values (Advanced)" section of the boost.signals tutorial: http://www.boost.org/doc/libs/1_48_0/doc/html/signals/tutorial.html#id3070223
Here's the gist of it:
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;
}
};
The input iterators to operator() refer to the collection of slots for the signal. Each time you dereference one of the iterators, it calls the slot. So you can let one of the slots return a value to indicate that it doesn't want any further slots to be called.
Then you just pass it in to the second template arg of your signal:
boost::signals2::signal<bool(ThingEvent), MyCombiner> sig;
Now you can implement thingHappened to return a bool value indicating whether or not you want the signal to be stopped.