3

Messageペイロードをバイナリにパックして元に戻すことができるクラスがあります。好き:

PayloadA p;
msg->Unpack(&p);

PayloadAクラスはどこですか。

問題は、私がたくさんのペイロードを持っているということです、それで私は巨人ifまたはswitch声明を必要とします:

if (msg->PayloadType() == kPayloadTypeA)
{
    PayloadA p;
    msg->Unpack(&p); // void Unpack(IPayload *);

    // do something with payload ...
}
else if ...

ペイロードを解凍するヘルパー関数を書きたいです。しかし、この関数のタイプは何でしょうか?何かのようなもの:

PayloadType UnpackPayload(IMessage *msg) { ... }

ここPayloadTypeで、typedefは適切なペイロードクラスです。私はそれが不可能であることを知っていますが、私はこのような解決策を探しています。何か案は?

ありがとう。

4

6 に答える 6

2

問題を完全に回避するために、1つ上のレベルに分割します。

#include <map>
#include <functional>

...
std::map<int, std::function<void()> _actions;
...

// In some init section
_actions[kPayloadA] = [](IMessage* msg) {
    PayloadA p;
    msg->Unpack(&p);

    // do something with payload ...
};
// repeat for all payloads

...

// decoding function
DecodeMsg(IMessage* msg) {
    _actions[id](msg);
}

コードサイズをさらに小さくするにはUnpack、関数テンプレートを作成してみてください(仮想でない場合にのみ簡単に可能です。仮想でない場合は、1レベルの間接参照を追加して、仮想にならないようにすることができます;):

class Message {
   template <class Payload>
   Payload Unpack() { ... }
};

auto p = msg->Unpack<PayloadA>();

// do something with payload ...

編集

次に、の長いリストを作成しないようにする方法を見てみましょう_actions[kPayloadN]。これは非常に重要です。

まず、静的初期化中(つまり、メインの前)にコードを実行するためのヘルパーが必要です。

template <class T>
class Registrable
{
    struct Registrar
    {
        Registrar()
        {
            T::Init();
        }
    };

    static Registrar R;

    template <Registrar& r>
    struct Force{ };
    static Force<R> F; // Required to force all compilers to instantiate R
                       // it won't be done without this
};

template <class T>
typename Registrable<T>::Registrar Registrable<T>::R;

次に、実際の登録ロジックを定義する必要があります。

typedef map<int, function<void()> PayloadActionsT;
inline PayloadActionsT& GetActions() // you may move this to a CPP
{
    static PayloadActionsT all;
    return all;
}

次に、解析コードを考慮します。

template <class Payload>
struct BasePayload : Registrable<BasePayload>
{
    static void Init()
    {
        GetActions()[Payload::Id] = [](IMessage* msg) {
             auto p = msg->Unpack<Payload>();
             p.Action();
        }
    }
};

次に、すべてのペイロードを1つずつ定義します

struct PayloadA : BasePayload<PayloadA>
{
    static const int Id = /* something unique */;
    void Action()
    { /* what to do with this payload */ }
}

最後に、着信メッセージを解析します。

void DecodeMessage(IMessage* msg)
{
    static const auto& actions = GetActions();
    actions[msg->GetPayloadType]();
}
于 2012-11-23T17:49:20.670 に答える
2

タイプに応じてペイロードを作成し、各ペイロードタイプのペイロードコンストラクターと組み合わせて、メッセージをパラメーターとして受け取るファクトリメソッドはどうですか?

スイッチ(または同様の構造)を回避することはできませんが、少なくともそれは簡単で、構造コードはスイッチとは別のものです。

例:

class PayloadA : public Payload
{
  public:
  PayloadA(const &Message m) {...} // unpacks from m
};

class PayloadB : public Payload
{
  public:
  PayloadB(const &Message m) {...} // as above
};

Payload * UnpackFromMessage(const Message &m)
{
  switch (m.PayloadType) :
  case TypeA : return new PayloadA(m);
  case TypeB : return new PayloadB(m);
  ... etc...
}
于 2012-11-23T18:03:31.087 に答える
1

私はこれが組合で解決されるのを見ました。ユニオンの最初のメンバーは、含まれているパケットのタイプです。

ここでの例:ユニオンとは何ですか?

于 2012-11-23T17:42:51.793 に答える
1

重要な問題は、ペイロードがどのように異なり、どのように同じであるかです。ペイロードによって決定されるタイプのオブジェクトを生成し、すべてのタイプのペイロードに共通の仮想インターフェースを介してそれらと対話するシステムは、場合によっては合理的です。

ペイロードのタイプの有限で固定されたリストがあると仮定した場合、aを返すのboost::variantは比較的簡単です。apply_visitor次に、それを処理するために、バリアント内のすべてのタイプを受け入れるファンクターで呼び出します。

1つのタイプのペイロードのみを異なる方法で処理する場合は、「タイプがTに一致する場合にのみラムダを呼び出して実行する」関数をこのように記述するのはそれほど難しくありません。

したがって、次のような構文を取得できます。

struct State;
struct HandlePayload
{
  typedef void return_type;
  State* s;
  HandlePayload(State* s_):s(s_) {}
  void operator()( int const& payload ) const {
    // handle int here
  }
  void operator()( std::shared_ptr<bob> const& payload ) const {
    // handle bob ptrs here
  }
  template<typename T>
  void operator()( T const& payload ) const {
    // other types, maybe ignore them
  }
}

これはかわいいですが、かなり間接的であることに気付くでしょう。ただし、ペイロードを処理するために上記のジェネリック型Tを使用してテンプレートコードを記述し、状況によってはトレイトクラスなどを使用したり、他の状況では明示的な特殊化を使用したりできることにも注意してください。

ペイロードが1つの特定の種類であると予想し、その場合に特別な作業のみを実行したい場合は、に単一タイプのハンドラーを作成するのboost::variantは簡単です。

template<typename T, typename Func>
struct Helper {
  typedef bool return_type;
  Func f;
  Helper(Func f_):f(f_) {}
  bool operator()(T const& t) {f(t); return true; }
  template<typename U>
  bool operator()(U const& u) { return false; }
};    
template<typename T, typename Variant, typename Func>
bool ApplyFunc( Variant const& v, Func f )
{
  return boost::apply_visitor( Helper<T, Func>(f), v );
}

これは、バリアントvでfを呼び出しますが、バリアントのタイプTでのみ呼び出し、タイプが一致する場合はtrueを返します。

これを使用すると、次のようなことができます。

boost::variant<int, double> v = 1.0;
boost::variant<int, double> v2 = int(1);
ApplyFunc<double>( v, [&](double d) { std::cout << "Double is " << d << "\n"; } );
ApplyFunc<double>( v2, [&](double d) { std::cout << "code is not run\n"; } );
ApplyFunc<int>( v2, [&](int i) { std::cout << "code is run\n"; } );

またはそのような変種。

于 2012-11-23T19:48:18.900 に答える
0

良い解決策の1つは、共通の基本クラス+そのクラスから継承するすべてのペイロードです。

class PayLoadBase {
  virtual char get_ch(int i) const=0;
  virtual int num_chs() const=0;
};

そして、開梱は次のようになります。

class Unpacker {
public:
   PayLoadBase &Unpack(IMessage *msg) {
      switch(msg->PayLoadType()) {
      case A: a = *msg; return a;
      case B: b = *msg; return b;
        ...
      }
   }
private:
   PayLoadA a;
   PayLoadB b;
   PayLoadC c;
};
于 2012-11-23T17:49:26.043 に答える
-1

関数にを返すようにすることができますvoid *。voidポインターは、他のタイプにキャストできます。

于 2012-11-23T17:41:36.660 に答える