3

ステート マシンを持つクラスがあり、イベントをステート マシンに渡す単一のエントリ ポイントが必要です。イベントにはイベント固有のデータが付随しており、これをハンドラーにディスパッチしたいと考えています。だから、それはこのようなものに見えます...

class X
{
  public:
    ...
    template<typename... A> void fsm(eEvent eventId, const A&... eventData);

  private:
    ...
    void eventA(int a, double b);
    void eventB(std::string a);
    void eventC(unsigned long a);

};

...このような呼び出しで...

X x;

x.fsm(eEventA, -1, 2.0);
x.fsm(eEventB, "xyz");
x.fsm(eEventC, 42);

テンプレート関数を取得して正しいハンドラーを呼び出す方法がわかりません。単純に eventId をオンにして可変引数を渡すと、すべてのパラメーターの組み合わせに対してハンドラーが存在しないため、コンパイルされません (たとえば、eventB() 引数を受け入れる eventA() ハンドラーはありません)。とにかく欲しい)。

これを行うためのエレガントな方法があると思いますが、それは私を避けています。

4

4 に答える 4

5

オプション 1.バリアディックを捨てる

C++11 テンプレート パラメーター パックを持っていない場合、自然な選択は、各イベントの型を宣言し、基本イベント型から継承し、その中にパッケージ化するeventDataことです。それが私がお勧めするものです。動的タイプを使用して、正しいものがディスパッチされていることを確認できます。それによって速度が低下する場合は、プロダクション モードでのチェックを無効にします。

オプション 2.PTMF

メンバー関数へのポインターを使用して、ディスパッチャーへのイベントを識別します。これにより、列挙が不要になり、ディスパッチのタイプがテンプレート化されたディスパッチャーに直接エンコードされます。

ただし、これには、ディスパッチされる関数が である必要がありますpublic

template<typename... P, typename... A> void fsm(void (X::*event)( P ... ),
    const A&... eventData) {
    this->*event( eventData ... );
}

x.fsm( &X::eventB, 5, 1.3 );
于 2013-01-15T16:52:32.147 に答える
0

eEventが整数に似ていて、コンパイル時に常に認識されている場合は、その上でテンプレートを作成できます。次に、呼び出しは次のようになります

x.fsm<eEventA>(1, 2.4);

関数テンプレートの特殊化に制限があるため、少し複雑になる可能性があります。また、おそらくジェネリックが必要になるでしょう。つまり、間違った引数パックはランタイムエラーになります(ただし、明確なエラーです)。

これが実際に有利かどうかはわかりません

x.onEventA(1, 2.4);
于 2013-01-16T04:59:14.083 に答える
0

あなたがやろうとしていることを正確に理解しているかどうかはわかりませんが、このテンプレートの特殊化のようなものは機能しますか?

#include <iostream>

// ----------------------------------
class EventA
{
public:
    int a;
    double b;
};

class EventB
{
public:
    std::string strEvent;
};

class EventC
{
public:
    unsigned long a;
};

// -------------------------
class X
{
public:
    template<typename EVENT_TYPE> void fsm (const EVENT_TYPE &eventData);

public:
    void eventA (int a, double b) {}
    void eventB (std::string a) {}
    void eventC (unsigned long a) {}
};


// -------------------------
template<typename TYPE>
struct invoker {};

template<>
struct invoker<EventA>
{
    static void call (X *x, EventA e)
    {
        x->eventA (e.a, e.b);
    }
};

template<>
struct invoker<EventB>
{
    static void call (X *x, EventB e)
    {
        x->eventB (e.strEvent);
    }
};

template<>
struct invoker<EventC>
{
    static void call (X *x, EventC e)
    {
        x->eventC (e.a);
    }
};


// ---------------------------------------
template<typename EVENT_TYPE> 
void X::fsm (const EVENT_TYPE &eventData)
{
    invoker<EVENT_TYPE>::call (this, eventData);
}


int main (int argc, char *argv[])
{
    X x;

    EventA eEventA;
    eEventA.a = -1;
    eEventA.b = 2.0;

    EventB eEventB;
    eEventB.strEvent = "xyz";

    EventC eEventC;
    eEventC.a = 42;

    x.fsm (eEventA);
    x.fsm (eEventB);
    x.fsm (eEventC);

    return 0;
}
于 2013-01-15T19:19:24.190 に答える
0

同じ設計(つまり、実行時に計算された整数値に基づいてディスパッチされるイベント)を維持することによって、必要なことを行う方法はありません。の値はeventId実行時にのみ認識され、呼び出される関数のシグネチャを決定しますが、引数のはコンパイル時に認識される必要があります。直接的または間接的に、関数は次のようになります。

if (eventId == eEventA) { eventA(eventData...); }
else if (eventId == eEventB) { eventB(eventData...); }
...

しかし、コンパイラはブランチを解析し、その署名と互換性のない呼び出しを確実に見つけるため、それは決して compileしません。ifeventX

そもそもこの設計を使用するのではなく、Boost.Signal と同様の方法でイベントを処理することをお勧めします。

于 2013-01-15T17:06:36.880 に答える