将值映射到类型以避免 C++ 中的 switch 语句 [英] Map value to type to avoid switch statement in C++

查看:23
本文介绍了将值映射到类型以避免 C++ 中的 switch 语句的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

最近我遇到了一个我自己无法解决的问题.假设我们通过函数从网络接收一些字节:

Recently I faced a problem that I could not manage on my own. Let's assume that we receive some bytes from network by function:

vector<char> receive (); 

当我得到字节时,我确切地知道前 10 个字节是什么,以及如何解释它们.消息的其余部分与 Id 中使用的值(这 10 个字节)相关联.所以,例如它可以这样表达:

When I get bytes I know precisely what are for example first 10 bytes, and how to interpret them. Rest of the message is connected with value being used in the Id (these 10 bytes). So, for example it can be expressed like this:

auto bytes = receive ();
Id id = get_id (bytes);

switch (id) { 
    case Id::Message1:
    {
        Message1 message = convert_bytes<Message1> (bytes);
        notify (message);
        break;
    }

    case Id::Message2:
    {
        Message2 message = convert_bytes<Message2> (bytes);
        notify (message);
        break;
    }
    ...

    case Id::MessageN:
    {
        MessageN message = convert_bytes<MessageN> (bytes);
        notify (message);
        break;
    }
}

如您所见,每个案例部分与其他案例部分的不同之处仅在于消息 ID 和消息类型.

As you see, each case section differs from others only with Message Id and type of Message.

我的问题是:是否有可能将 Id 值映射到特定类型的 Message 以使上述代码更简单并且不使用 switch 语句?

auto bytes = receive ();
Id id = get_id (bytes);

// Here some magic retrieval of Message type based on Id value and invoking 
// appropriate convert_bytes specialization
// auto message = convert_bytes<get_message_type(id)> (bytes); (Y)

当然 (Y) 是错误的,但也许你知道一些其他的方法来做这个概念.

Of course (Y) is wrong, but maybe you know some other way of doing the concept.

我知道这种将 Id 值连接到类型的逻辑必须在某处实现,但是将 Id 值连接到类型比为每个 Message 类型编写 case 部分要简单得多.

I know that this logic of connection Id value to the type has to be implemented somewhere, but it would be much simpler just to connect Id value to the type than writing case section for each Message type.

我也知道,我可能可以做这样的事情:

I know also, that I probably could do something like this:

class Base {};
class Message1 : Base {}
...
class MessageN : Base {}

vector<pair<Id, Base*>> vec;

但我不知道这是否是好的/有效的方法,因为每次我想将 Base* 转换为 MessageX* 时我都应该使用 dynamic_cast.

but I don't know if this is good/efficient way since I should use dynamic_cast each time I would like to convert Base* to MessageX*.

我试着用元组和初始化列表做一些包装类,像这样:

I tried to do some wrapper class with tuple and initializer_list, something like this:

struct Message1 { int x; };
struct Message2 { double z; };

enum class Id { Message1, Message2 };

template <typename Id_type, typename ... Types>
class Wrapper { 
    public:
        Wrapper (tuple<Types ...> t, const initializer_list<Id_type>& il) : tpl (t), ids (il) {


        }

       MessageType_depended_on_id get_message (Id id, vector<char> bytes); // (X)


    private:
        tuple<Types ...> tpl;
        vector<Id_type> ids;
};

tuple<Message1, Message2> tpl;
Wrapper<Id, Message1, Message2> wrapper (tpl, {Id::Message1, Id::Message2});

(X) 但是没有办法根据 id 值指定成员函数类型,还是我遗漏了什么?

(X) But there is no way to specify member function type depended on the id value, or am I missing something?

我最后的想法是在模板专业化中实现每个案例部分,如下所示:

My last thought was to implement each case section in template specialization, something like this:

template <Id id> 
class Type_retriever {};

template <>
class Type_retriever<Id::Message1> {
    public:
        static Message1 get_msg (const vector<char>& bytes) { 
            cout << "Message1" << endl;
            return convert_bytes<Message1> (bytes);
        }
};

template <>
class Type_retriever<Id::Message2> {
    public:
        static Message2 get_msg (const vector<char>& bytes) { 
            cout << "Message2" << endl;
            return convert_bytes<Message2> (bytes);
        }
};

template <typename Type>
void notify (Type message) { }

auto bytes = receive ();
auto id = get_id (bytes);
notify (Type_retriever<id>::get_msg (bytes));

但这不会编译,因为id"的值在常量表达式 (gcc) 中不可用,这可以理解为什么无法编译.

But this will not compile because the value of ‘id’ is not usable in a constant expression (gcc) which is understandable why this can not compile.

如果您有任何建议,那就太好了.

If you have any suggestion, it would be great.

推荐答案

您可以使用注册/插件机制摆脱 switch 语句.

You can get rid of the switch statement using a registration/plugin mechanism.

用于注册和使用函数的接口:

Interface for registering functions and using them:

typedef void (*MessageDispatcher)(const vector<byte>& bytes);

void registerMessageDispatcher(Id id, MessageDispatcher dispatcher);

void dispatchMessage(Id id, const vector<byte>& bytes);

在实现中:

static std::map<Id, MessageDispatcher> messageDispatcherMap;

void registerMessageDispatcher(Id id, MessageDispatcher dispatcher)
{
   messageDispatcherMap[id] = dispatcher;
}

void dispatchMessage(Id id, const vector<byte>& bytes)
{
   std::map<Id, MessageDispatcher>::iterator iter = messageDispatcherMap.find(id);
   if ( iter == messageDispatcherMap.end() )
   {
      // Deal with the error condition.
      return;
   }

   // Dispatch the message.
   iter->second(bytes);

}

为各种消息类型创建函数.

Create functions for various message types.

void dispatchMessage1(const vector<byte>& bytes)
{
   Message1 message = convert_bytes<Message1> (bytes);
   notify (message);
}

void dispatchMessage2(const vector<byte>& bytes)
{
   Message2 message = convert_bytes<Message2> (bytes);
   notify (message);
}

void dispatchMessage3(const vector<byte>& bytes)
{
   Message3 message = convert_bytes<Message3> (bytes);
   notify (message);
}

等等...

注册函数.

registerMessageDispatcher(ID::Message1, dispatchMessage1);
registerMessageDispatcher(ID::Message2, dispatchMessage2);
registerMessageDispatcher(ID::Message3, dispatchMessage3);

现在,处理消息的代码是:

Now, the code to deal with the message will be:

auto bytes = receive ();
Id id = get_id (bytes);
dispatchMessage(id, bytes);

这篇关于将值映射到类型以避免 C++ 中的 switch 语句的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆