3

我有一个Message类能够将其有效负载打包为二进制文件并将其解压缩。喜欢:

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

在哪里PayloadA上课。

问题是我有一堆有效载荷,所以我需要巨人ifswitch声明:

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

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

我想编写一个解包有效负载的辅助函数。但是这个函数的类型是什么?就像是:

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

哪里PayloadTypetypedef适当的有效载荷类。我知道这是不可能的,但我正在寻找这样的解决方案。有任何想法吗?

谢谢。

4

6 回答 6

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 回答
2

我会再上一层来完全避免这个问题:

#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一个函数模板(只有在它不是虚拟的情况下才可以轻松实现,如果是,您可以尝试添加一层间接使其不是;):

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

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

// do something with payload ...

编辑

现在让我们看看如何避免编写长长的_actions[kPayloadN]. 这是非常重要的。

首先,您需要一个助手在静态初始化期间(即在 main 之前)运行代码:

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();
        }
    }
};

然后我们一一定义所有的payload

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 回答
1

一个重要的问题是有效载荷有何不同,以及它们有何相同之处。在某些情况下,您可以生成由有效负载确定类型的对象,然后通过所有有效负载类型通用的虚拟接口与它们交互的系统。

另一个选项假设您有一个有限且固定的有效负载类型列表,返回 aboost::variant相对容易。然后为了处理它,调用apply_visitor一个接受变体中每种类型的函子。

如果您只想以不同的方式处理一种类型的有效负载,那么“当且仅当类型与 T 匹配时调用并运行 lambda”函数并不难以这种方式编写。

所以你可以得到这样的语法:

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 编写模板代码来处理有效负载,并在某些情况下使用诸如特征类之类的东西,或者在其他情况下使用显式特化。

如果您希望有效负载是一种特定类型,并且只想在这种情况下做一些特殊工作,那么在 a 上编写单一类型的处理程序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 回答
1

我看到工会解决了这个问题。联合的第一个成员是包含的数据包类型。

这里的例子:什么是联合?

于 2012-11-23T17:42:51.793 回答
0

一个好的解决方案是一个通用的基类 + 从该类继承的所有有效负载:

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 回答