动态定义函数返回类型

Dynamically define a function return type

本文关键字:返回类型 函数 定义 动态      更新时间:2023-10-16

我有一个Message类,它能够将其有效负载打包为二进制并将其拆包回来。类似:

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

其中CCD_ 2是一个类。

问题是我有一堆有效载荷,所以我需要巨大的ifswitch语句:

if (msg->PayloadType() == kPayloadTypeA)
{
PayloadA p;
msg->Unpack(&p); // void Unpack(IPayload *);
// do something with payload ...
}
else if ...

我想编写一个帮助器函数,用于解包有效负载。但是这个函数的类型是什么呢?类似于:

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

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

谢谢。

我会将级别提高一级以完全避免问题:

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

工厂方法如何根据类型创建有效载荷,并结合每个有效载荷类型的有效载荷构造函数,将消息作为参数?

无法避免切换(或一些类似的构造),但至少它很简单,并且构造代码与切换是分开的。

示例:

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

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

这里的例子:什么是工会?

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

另一个选项是,假设您有一个有限且固定的有效载荷类型列表,则返回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,但仅在变量中的类型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 runn"; } );
ApplyFunc<int>( v2, [&](int i) { std::cout << "code is runn"; } );

或者一些这样的变体。

一个好的解决方案是一个公共基类+从该类继承的所有有效载荷:

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

您可以使函数返回一个void *。void指针可以强制转换为任何其他类型。