C++14下针对事件类层级验证可变参std::function回调参数合法性
C++14 事件系统注册期参数校验实现方案
核心思路
为每个事件子类绑定专属的回调参数类型定义,优先通过编译期静态校验保证参数匹配,必要时补充运行时校验逻辑,将参数不匹配的错误提前到注册阶段暴露,完全规避触发时静默失败问题。
具体实现步骤
1. 改造事件类层级,绑定参数类型
为每个事件子类增加固定的回调参数类型别名,可选增加运行时参数类型查询接口:
// 增加标准库依赖头文件 #include <type_traits> #include <typeinfo> #include <stdexcept> enum class EventKind { first_kind, second_kind, third_kind }; class EventBase { public: virtual EventKind kind() const = 0; virtual bool matches(const EventBase&) const = 0; // 可选:运行时参数类型查询接口,用于多态场景校验 virtual const std::type_info& callback_args_type() const = 0; // 可选:克隆接口,用于多态场景下的事件对象拷贝 virtual EventBase* clone() const = 0; virtual ~EventBase() = default; }; class FirstEvent : public EventBase { public: // 定义该事件对应的回调参数类型,按需修改为实际参数列表 using CallbackArgs = std::tuple<int, const std::string&>; EventKind kind() const final { return EventKind::first_kind; } bool matches(const EventBase&) const final; const std::type_info& callback_args_type() const final { return typeid(CallbackArgs); } FirstEvent* clone() const final { return new FirstEvent(*this); } }; // 其他事件子类按相同规则增加CallbackArgs别名和对应接口实现 class SecondEvent : public EventBase { public: using CallbackArgs = std::tuple<uint32_t, double>; EventKind kind() const final { return EventKind::second_kind; } bool matches(const EventBase&) const final; const std::type_info& callback_args_type() const final { return typeid(CallbackArgs); } SecondEvent* clone() const final { return new SecondEvent(*this); } };
2. 实现编译期静态校验的注册接口
修改EventManager的add_listener为模板接口,直接接收具体事件子类实例,通过std::enable_if约束参数匹配,不匹配的代码会直接编译报错:
class EventManager { public: // 优先使用的接口:接收具体事件子类,编译期校验参数匹配 template<typename EventT, typename... Args, typename = typename std::enable_if< std::is_base_of<EventBase, EventT>::value && std::is_same<std::tuple<Args...>, typename EventT::CallbackArgs>::value >::type> void add_listener(const EventT& event, std::function<void(Args...)> listener) { auto sub = new Sub<Args...>(); sub->event = new EventT(event); sub->listener = std::move(listener); subscriptions[event.kind()].push_back(sub); } // 可选:兼容接收EventBase基类的多态场景,注册期运行时校验 template<typename... Args> void add_listener(const EventBase& event, std::function<void(Args...)> listener) { // 注册时直接校验参数类型,不匹配立即抛出异常/触发断言 if (typeid(std::tuple<Args...>) != event.callback_args_type()) { throw std::invalid_argument("Listener argument type mismatch with event"); } auto sub = new Sub<Args...>(); sub->event = event.clone(); sub->listener = std::move(listener); subscriptions[event.kind()].push_back(sub); } // 可选:给trigger也增加参数校验逻辑,避免触发时参数错误 template<typename EventT, typename... Args, typename = typename std::enable_if< std::is_base_of<EventBase, EventT>::value && std::is_same<std::tuple<Args...>, typename EventT::CallbackArgs>::value >::type> void trigger(const EventT& event, Args... args) { for (auto s : subscriptions[event.kind()]) { if (event.matches(*(s->event))) { auto* ss = dynamic_cast<Sub<Args...> *>(s); if (ss && ss->listener) { ss->listener(args...); } } } } ~EventManager() { // 补充析构逻辑避免内存泄漏 for (auto& pair : subscriptions) { for (auto sub : pair.second) { delete sub; } } } private: struct SubBase { EventBase* event; virtual ~SubBase() { delete event; } }; template<typename... Args> struct Sub : public SubBase { std::function<void(Args...)> listener; }; std::map<EventKind, std::vector<SubBase *>> subscriptions; };
兼容说明
所有实现仅使用C++14标准特性,完全兼容Visual Studio 2015、GCC 4.9.2运行环境。
内容的提问来源于stack exchange,提问作者Danny S
相关产品推荐
相关产品推荐

