Я бы хотел использовать 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
, который будет вызывать 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