Любой способ отменить распространение сигнала в форсированных сигналах2 без исключения? - PullRequest
0 голосов
/ 08 декабря 2011

Я бы хотел использовать 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

1 Ответ

2 голосов
/ 09 декабря 2011

Вы можете сделать это с помощью пользовательского объединителя сигналов. Это объясняется в разделе «Значения возвращаемого сигнала (Дополнительно)» учебного пособия 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, указывающее, хотите ли вы, чтобы сигнал был остановлен.

...