我有一个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
。我知道这是不可能的,但我正在寻找这样的解决方案。有什么想法吗?
感谢。
答案 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指针可以转换为任何其他类型。