4

次のようなモジュールシステムを作成しました。

//setting event
module->set_event("started", [](boost::any ev) {
  cout << "The module have been successfully started" << endl;
});

//somewhere else
module->start();

//impl
void Module::start() {
  //run once protection here
  this->trigger_event("start");//pre start
  this->_impl->start(); //on error, throw exception
  this->trigger_event("started"); //post start
}

void Module::trigger_event(string str,boost::any ev = boost::any() )
{
  //mutex
  //invokes every handler which have been invoked set_event
}

本当にシンプルでわかりやすい。最初の欠点は「remove_event」がないことですが、(必要な場合) それを許可するには、呼び出し時にハンドラーを削除する std::function (set_event メソッドで) を返すことができます。イベント、返されたハンドラーを呼び出すだけです:

function<void ()> h = module->set_event(...);
//... somewhere later
h() //removes the handler safely

しかし、これが良いデザインなのか気になります。それとも、私のアプローチを使用して非常に大きな欠点を突然発見し、ほとんどのコードを書き直す必要がありますか? 「trigger_event」メソッドもモジュール間のメッセージ システムとして使用されると言えます。これは、JavaScript ライブラリの jQuery メソッド「bind」が許可するのと同じです。例:

module->set_event("message", [](boost::any message) {
 //deal with the message here
});

//send message
module->trigger_event("message", MyMessageTypeHere);

ほとんどの場合、これは受け入れられる設計ですか? 非常に柔軟なアプローチが必要なので、boost::any を使用するのが私の考えですが、これは一般的に良い考え (スケーラブル、パフォーマンス、柔軟性) ですか?

4

2 に答える 2

4

私が変更するいくつかのことがあります。

まず、リターンを少し無視して、それとそれがboost::any混ざったものに注目しましょうstd::string。これは、潜在的なランタイム タイプの不一致を許容するため、イベント システムにとっては非常に悪い考えです。この種の間違いを犯す可能性のある設計は必要ありません (最終的には間違い発生、修正が必要なバグが発生し、将来の時間が無駄になるためです)。

通常、本当に必要なものは次のとおりです。

void Module::trigger_event(shared_ptr<Event> const& event)
{
    // Event is a base interface
    // pull off event->ID() to get identifier and lookup
    // dispatch to something that has the signature
    //   void (shared_ptr<SpecificEvent>)
}

ディスパッチャーのようなことをするには、通常、

map<IDType, shared_ptr<Dispatcher>> dispatchDictionary_;

template <typename SpecificEvent>
class SpecificDispatcher : public Dispatcher
{
public:
    SpecificDispatcher(function<void (shared_ptr<SpecificEvent>)> handler)
        handler_(handler)
    {}

    virtual void dispatch(shared_ptr<Event> event)
    {
        auto specificEvent(static_ptr_cast<SpecificEvent>(event));

        handler_(specificEvent);
    }
};

(明白な方法で定義されたインターフェイス クラスと登録/登録解除メソッドを使用して、マップ内のイベント タイプ ID を関連付けます)。ポイントは、ID をクラス内の EventType に関連付け、常に同じ関連付けになるようにし、ハンドラーがデータ自体を再解釈する必要がない (エラーが発生しやすい) 方法でディスパッチすることです。ライブラリ内で自動化できる作業を実行して、外部で間違って実行されないようにします。

さて、あなたのメソッドで何を返しますか? 関数ではなく、オブジェクトを返します。明示的に呼び出す必要がない場合は、別の潜在的なエラーの原因を回避できます。これはすべての RAII と同じです。削除、ロック解除、または ... 同様に、「unregister_event」(またはそれを何と呼ぶか​​) を呼び出すことを覚えておく必要はありません。

プログラムには、式、関数スコープ、状態、およびプログラムの 4 つの有効期間があります。これらは、返されたオブジェクトを格納できる 4 つのタイプのものに対応しています。これは、2 つの非同期遷移イベントの間に存在する State クラス (State パターン内) のメンバーであるか、終了するまで待機しているグローバル スコープ オブジェクトです。

すべてのライフタイム管理は RAII で管理する必要があります。これは一種のデストラクタのポイントであり、例外に直面してリソースのクリーンアップを管理する方法です。


編集:私は、あなたが「明白な方法で」ドットを接続することを指摘して、述べられていない断片の束をちょっと残しました. しかし、私はもっと多くの部分を埋めようと思っていました (私は OS のビルドとインストールを行っており、私のバグは現在、インストールを待っている最後の 1 つにまで下がっています...)

ポイントは、誰かがただタイプできるべきだということでした

callbackLifetimeObject = module->set_event<StartEvent>([](shared_ptr<StartEvent> event){
    cout << "Module started: " << event->someInfo() << endl;
});

そのため、set_event にはこれを取得するための種類の署名が必要であり、適切なディスパッチャーを辞書に挿入する必要があります。ここで型から ID を取得するには、いくつかの方法があります。「明白な」方法は、一時的なものを作成してそれを「ID」メンバーと呼ぶことですが、これにはオブジェクト作成のオーバーヘッドがあります。別の方法は、それを「仮想静的」に変換し、静的を取得することです(これは、仮想メソッドが行うすべてのことです)。仮想静的があるときはいつでも、それらを特性に変える傾向があります-同じことですが、カプセル化がわずかに改善され、「すでに閉じられているクラス」も変更できます。次に、仮想メソッドは特性クラスも呼び出して同じものを返します。

そう...

template <typename EventType>
struct EventTrait
{
    // typedef your IDType from whatever - looks like you want std::string
    static IDType eventID()
    { /* default impl */ }
};

template <>
struct EventTrait<StartEvent>
{
    // same as above
    static IDType eventID()
    { return "Start"; }
};

その後、あなたはすることができます

template <typename EventType>
EventRegistration set_event(function<void (shared_ptr<EventType>)> handler)
{
    auto id(EventTrait<EventType>::eventID());

    dispatchDictionary_.insert(make_pair(id, 
        make_shared<SpecificDispatcher<EventType>>(handler)));

    return EventRegistration(bind(& Module::unset_event, this, id));
}

これは、ピースを組み合わせる方法と、いくつかのオプションをよりよく示しているはずです。これのいくつかは、各イベントで再コード化されるボイラープレート コードを示しています。これは、おそらく自動化したいと思うタイプのものです。このような生成をメタプログラミングするための高度なテクニックは、仕様を必要とする別のビルド ステップでの作業から、C++ メタプログラミング システム内での作業まで、たくさんあります。繰り返しますが、以前のポイントと同様に、自動化できるほど、バグが少なくなります。

于 2012-04-06T01:14:33.653 に答える
1

2 つの理由で問題があります。

まず、イベントを 1 つのパラメーターを受け取るハンドラーに制限しますが、ユースケースから判断すると、さまざまな種類のイベントが発生する可能性があります。したがって、これは制限です (はい、いくつかのパラメーターをエンコードするために、任意の型に をスローできstd::tupleますが、本当にこれが必要ですか?)。

さらに重要なことに、型安全性を弱めることは、まあ、弱められた (型) 安全性を伴います。イベント ハンドラーがヤギでしか動作しない場合は、子羊を渡さないでください。trigger_event任意の数の引数を受け取る型関数を作成できるため、(少なくとも C++11 では) テンプレートを使用して、必要なことを実行できるようです。次に、この引数を使用してハンドラーを呼び出そうとしますが、正しい数と型の引数を指定しなかった場合、コンパイル エラーが発生します。

ここで、ディスパッチャで型解決を非表示にできますが、これは少しトリッキーです。これは、任意の型のイベントを格納する必要があるためです。これは使用する場所になる可能性がありますが、スケジューラの実装/ロジックanyに保持され、ユーザーに漏れることはありません。

編集:この解決策を達成するために、私のアプローチは、いくつかの内部抽象Event型(ポインターを保存できる)とtemplate <typename Hnd> classs ConcreteEvent : public Event、顧客のイベントハンドラーを保持できるもの(がメソッドのテンプレートパラメーターであるとConcreteEvent<EventType>仮定)を持つことです。rtti を使用すると、後で取得するときに ( としてのみ取得します)、呼び出したいタイプと一致するかどうかを確認できます。EventTypeset_eventEvent*

于 2012-04-06T01:07:56.543 に答える