动态定义函数返回类型

时间:2012-11-23 17:35:30

标签: c++ function return-type

我有一个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) { ... }

其中PayloadType是适当有效负载类的typedef。我知道这是不可能的,但我正在寻找这样的解决方案。有什么想法吗?

感谢。

6 个答案:

答案 0 :(得分: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();
        }
    }
};

然后我们逐个定义所有有效载荷

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

答案 1 :(得分:2)

如何根据类型创建有效负载的Factory方法,以及每个有效负载类型的有效负载构造函数,将消息作为参数?

没有避免切换(或类似的构造),但至少它很简单,构造代码与交换机是分开的。

示例:

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...
}

答案 2 :(得分:1)

我看到这与工会解决了。 union的第一个成员是包含的数据包类型。

此处的示例:What is a union?

答案 3 :(得分:1)

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

假设您有一个有限且固定的有效载荷类型列表,返回boost::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编写模板代码来处理有效负载,并在某些情况下使用traits类等内容,或者为其他情况使用显式专门化。

如果您希望有效负载是一种特殊类型,并且只想在这种情况下做一些特殊工作,那么在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,但仅在Variant中调用类型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"; } );

或某些此类变体。

答案 4 :(得分: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;
};

答案 5 :(得分:-1)

您可以让函数返回void *。 void指针可以转换为任何其他类型。