You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现模板化AcceptFunctions消息处理接口及constexpr函数映射?

问题背景与需求

现有一套消息处理代码,消息处理接口与消息定义相互独立,代码如下:

现有代码文件

msg_io.h

/**
 * @file msg_io.h
 */
struct MsgIO { virtual ~MsgIO() {} };

handler.h

/**
 * @file handler.h
 */
#include <map>
#include "msg_io.h"

template<class _msgHandler>
struct HandlerInterface 
{
    using HandlerFunc = bool (int, const MsgIO& );
    using HandlerFuncPtr = HandlerFunc _msgHandler::*;

    template<class T> using AcceptFunc = bool(int, const T*);
    template<class T> using AcceptFuncPtr = AcceptFunc<T> _msgHandler::*;

    using HandlerMap = std::map<unsigned, HandlerFuncPtr>;
};

server.h

/**
 * @file server.h
 */  
#include "handler.h"
struct ConcreteHandler : HandlerInterface<ConcreteHandler>
{
    enum Messages { MSG_1, MSG_2, MSG_3 };

    // 疑问:这样的声明合规吗?能否放到模板里?
    AcceptFunc<struct Msg1> accept;
    AcceptFunc<struct Msg2> accept;
    AcceptFunc<struct Msg3> accept;

    template<class T>
    bool acceptMsg(int ind, const MsgIO& m)
    {
        auto mp = dynamic_cast<const T*>(&m);
        return mp ? accept(ind, mp) : false;
    }

    static HandlerMap handlers;

    template<class T>
    static const constexpr HandlerFuncPtr Handle = &ConcreteHandler::acceptMsg<T>;
};

messages.h

/**
 * @file messages.h
 */
#include "msg_io.h"  

struct Msg1: MsgIO {};
struct Msg2: MsgIO {};
struct Msg3: MsgIO {};

handlermap.cpp

/**
 * @file handlermap.cpp
 */
#include "server.h"
#include "messages.h"  
// 疑问:能否改成constexpr?
ConcreteHandler::HandlerMap ConcreteHandler::handlers{
    { MSG_1,        Handle<Msg1> },
    { MSG_2,        Handle<Msg2> },
    { MSG_3,        Handle<Msg3> },
    { MSG_10,       Handle<Msg1> }
};

handler.cpp

/**
 * @file handler.cpp
 */
#include "server.h"
#include "messages.h"  

bool ConcreteHandler::accept(int, const Msg1*) { return true; }
bool ConcreteHandler::accept(int, const Msg2*) { return true; }
bool ConcreteHandler::accept(int, const Msg3*) { return true; }

当前可通过ConcreteHandler::handlers.find()根据消息编号获取对应的acceptMsg函数。现需完成以下改造:

  1. 将实现重构为形如AcceptFunctions<Msg1, Msg2, Msg3>::accept的模板化接口,同时配套对应的AcceptFunctions<Msg1, Msg2, Msg3>::handlers;
  2. 确认是否可以将函数映射表以constexpr方式在编译期完成初始化。

解决方案与解答

1. 模板化接口改造:AcceptFunctions可变参数模板

我们可以用可变参数模板实现通用的AcceptFunctions,自动适配任意数量的消息类型,同时保留原有的消息处理逻辑:

改造后的核心代码

通用handler基模板

#include <map>
#include <type_traits>
#include "msg_io.h"

// 前置声明
template<typename... Msgs>
struct AcceptFunctions;

// 递归终止:无消息类型的情况
template<>
struct AcceptFunctions<> {};

// 可变参数模板实现
template<typename FirstMsg, typename... RestMsgs>
struct AcceptFunctions<FirstMsg, RestMsgs...> : AcceptFunctions<RestMsgs...>
{
    // 继承基类的accept重载,实现多消息类型的accept
    using AcceptFunctions<RestMsgs...>::accept;

    // 当前消息类型的accept函数签名
    using AcceptFunc = bool(int, const FirstMsg*);
    AcceptFunc accept;

    // 通用的消息转发函数,和原acceptMsg逻辑一致
    template<typename T>
    bool acceptMsg(int ind, const MsgIO& m)
    {
        if constexpr (std::is_base_of_v<MsgIO, T>)
        {
            if (const auto* mp = dynamic_cast<const T*>(&m))
            {
                return accept(ind, mp);
            }
        }
        return false;
    }

    // 消息处理函数指针类型
    using HandlerFunc = bool(int, const MsgIO&);
    using HandlerFuncPtr = HandlerFunc AcceptFunctions::*;

    // 模板化的Handle指针,指向当前类型的acceptMsg
    template<typename T>
    static constexpr HandlerFuncPtr Handle = &AcceptFunctions::acceptMsg<T>;

    // 消息映射表类型
    using HandlerMap = std::map<unsigned, HandlerFuncPtr>;
    static HandlerMap handlers;
};

// 初始化映射表的辅助函数
template<typename... Msgs>
typename AcceptFunctions<Msgs...>::HandlerMap 
init_handlers(const std::initializer_list<std::pair<unsigned, typename AcceptFunctions<Msgs...>::HandlerFuncPtr>>& init)
{
    return typename AcceptFunctions<Msgs...>::HandlerMap(init);
}

使用示例

// 定义具体的消息处理类型
using MyMsgHandler = AcceptFunctions<Msg1, Msg2, Msg3>;

// 实现各个消息的accept函数
bool MyMsgHandler::accept(int ind, const Msg1*) { /* 处理Msg1逻辑 */ return true; }
bool MyMsgHandler::accept(int ind, const Msg2*) { /* 处理Msg2逻辑 */ return true; }
bool MyMsgHandler::accept(int ind, const Msg3*) { /* 处理Msg3逻辑 */ return true; }

// 初始化映射表
template<>
MyMsgHandler::HandlerMap MyMsgHandler::handlers = init_handlers<Msg1, Msg2, Msg3>({
    { 1, MyMsgHandler::Handle<Msg1> },
    { 2, MyMsgHandler::Handle<Msg2> },
    { 3, MyMsgHandler::Handle<Msg3> },
    { 10, MyMsgHandler::Handle<Msg1> }
});

原代码中accept重载的合规性说明:原ConcreteHandler中重复声明accept是合规的——这是C++的函数重载,只要每个accept的参数列表不同(这里是const Msg1*/const Msg2*等不同类型),编译器就能正确区分,且这种写法可以放到模板中(如上面的AcceptFunctions模板所示)。

2. 映射表的constexpr编译期初始化

限制说明

标准库的std::map在C20及之前都不支持constexpr构造(C23开始部分支持,但仍受限于动态内存分配的constexpr支持),因此无法直接用constexpr初始化std::map类型的handlers。

替代方案:编译期构建静态数组,运行时转为map/直接查找

我们可以用constexpr std::array存储键值对,在编译期完成初始化,运行时再将其转为std::map,或者直接用数组做线性查找(如果消息编号不多,效率足够):

constexpr数组实现

#include <array>
#include <utility>

// 定义constexpr键值对类型
template<typename HandlerPtr>
using ConstexprHandlerEntry = std::pair<unsigned, HandlerPtr>;

// 在AcceptFunctions模板中添加constexpr数组
template<typename FirstMsg, typename... RestMsgs>
struct AcceptFunctions<FirstMsg, RestMsgs...> : AcceptFunctions<RestMsgs...>
{
    // ... 保留之前的代码 ...

    // 编译期初始化的键值对数组
    template<ConstexprHandlerEntry<HandlerFuncPtr>... Entries>
    static constexpr auto make_constexpr_handlers()
    {
        return std::array<ConstexprHandlerEntry<HandlerFuncPtr>, sizeof...(Entries)>{Entries...};
    }

    // 示例:编译期初始化的静态数组
    static constexpr auto constexpr_handlers = make_constexpr_handlers<
        {1, Handle<Msg1>},
        {2, Handle<Msg2>},
        {3, Handle<Msg3>},
        {10, Handle<Msg1>}
    >();
};

// 运行时将constexpr数组转为std::map
template<>
MyMsgHandler::HandlerMap MyMsgHandler::handlers = [](){
    HandlerMap map;
    for (const auto& entry : MyMsgHandler::constexpr_handlers)
    {
        map.insert(entry);
    }
    return map;
}();

这种方式下,constexpr_handlers是完全在编译期初始化的,运行时只是将其拷贝到std::map中,既满足了编译期构建的需求,又保留了std::map的高效查找特性。

如果消息数量较少,也可以直接在编译期通过constexpr函数实现查找逻辑,完全避免运行时的map开销:

// 编译期查找函数
template<typename... Msgs>
constexpr typename AcceptFunctions<Msgs...>::HandlerFuncPtr 
find_handler(unsigned msg_id)
{
    for (const auto& entry : AcceptFunctions<Msgs...>::constexpr_handlers)
    {
        if (entry.first == msg_id)
        {
            return entry.second;
        }
    }
    return nullptr;
}

// 使用示例(编译期调用)
constexpr auto handler_for_msg1 = find_handler<Msg1, Msg2, Msg3>(1);

内容的提问来源于stack exchange,提问作者Swift - Friday Pie

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 08:37:03