0

boost::signals2C++ アプリでイベント通知を処理するために使用したいと思います。ブラウザー DOM イベントと同様の機能を実装したいと考えています。具体的には、イベントの伝播を停止して、現在のレシーバーがシグナルについて知る最後のレシーバーになり、後続のレシーバーが呼び出されないようにする機能です。(これがブラウザーでどのように機能するかについて詳しくは、 http://www.w3.org/TR/DOM-Level-3-Events/#events-event-type-stopImmediatePropagationを参照してください)

と呼ばれる信号を持つ仮想AppクラスがありthingHappenedます。通知を受信するさまざまなタイプの他のクラスがAppいくつかあるため、インスタンスは1 つしかない可能性があります。他の に通知されないように、ウィジェットが を消費 (停止) したい場合があります。WidgetconnectthingHappenedThingEventThingEventWidget

最初は a でこれを達成できるかどうか疑問に思っていましたshared_connection_blockが、これは一度に 1 つの接続しか抑制しないことがわかりました。最初にシグナルに a を渡しましたshared_ptr<ThingEvent>が、シグナルが呼び出されると、その伝播に介入する方法がありませんでした。shared_ptr を渡すと、シグナル レシーバーがイベントの値をチェックし、設定されている場合は返すことができますが、その詳細をライブラリのユーザーにプッシュしたくありません。

私が見つけた解決策はThingEvent、スタックに a を渡して、各レシーバーにコピーされるようにすることです。イベントを設定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に を呼び出すサンプル シグナル コンシューマ a を次に示します。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にあります。

4

1 に答える 1

2

これは、カスタム シグナル コンバイナーを使用して行うことができます。これらについては、boost.signals チュートリアルの「シグナルの戻り値 (上級)」セクションで説明されています。

その要点は次のとおりです。

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() への入力反復子は、シグナルのスロットのコレクションを参照します。イテレータの 1 つを逆参照するたびに、スロットが呼び出されます。したがって、スロットの 1 つに値を返させて、それ以上スロットを呼び出したくないことを示すことができます。

次に、シグナルの 2 番目のテンプレート引数に渡すだけです。

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

これで、thingHappened を実装して、信号を停止するかどうかを示す bool 値を返すことができます。

于 2011-12-08T21:49:34.610 に答える