【问题标题】:Map value to type to avoid switch statement in C++将值映射到类型以避免 C++ 中的 switch 语句
【发布时间】:2014-12-09 05:39:12
【问题描述】:

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

vector<char> receive (); 

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

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 和消息类型。

我的问题是:是否有可能将 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) 是错误的,但也许你知道一些其他的方法来实现这个概念。

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

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

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

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

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

我尝试用 tuple 和 initializer_list 做一些包装类,如下所示:

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 值指定成员函数类型,还是我遗漏了什么?

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

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)中使用,这是可以理解的为什么不能编译。

如果你有什么建议,那就太好了。

【问题讨论】:

  • 您正在寻找工厂模式。您不会将值映射到类型,而是将值映射到执行反序列化的函数/对象。映射可以通过 std::map 完成。查看其他序列化库/框架以获取灵感。

标签: c++ templates switch-statement


【解决方案1】:

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

函数注册和使用接口:

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

}

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

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

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-06-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多