C++技术问询:静态多态类constexpr自加入子类列表及事件订阅优化
优化事件子类的自动注册与统一调用方案
针对你遇到的手动调用init()容易遗漏、希望实现静态多态类自动加入constexpr列表的问题,我给你提供一套基于C++模板元编程和静态注册机制的解决方案,既能避免手动调用的疏漏,也能满足编译期constexpr的需求。
一、核心思路:自动注册+统一调度
我们的目标是让每个事件子类在定义时自动把自己的初始化/销毁逻辑注册到全局容器,不需要手动编写A::init(); B::init();这类代码,后续新增子类也无需修改调用逻辑。
1. 定义事件子类的约束(C++20+ 可选)
先通过concept约束所有事件子类必须实现指定的静态init()和成员kill(ID)方法,确保类型安全:
#include <concepts> #include <functional> #include <vector> #include <tuple> template<typename T> concept EventHandler = requires { { T::init() } -> std::same_as<void>; { std::declval<T>().kill(int{}) } -> std::same_as<void>; // 假设ID为int类型,可根据实际调整 };
2. 实现自动注册器(解决遗忘调用问题)
用Nifty Counter Idiom解决静态初始化顺序问题,确保注册容器在所有子类注册前就已就绪:
namespace event_detail { // 全局注册容器单例,管理所有init和kill函数 class HandlerRegistry { public: static HandlerRegistry& instance() { static HandlerRegistry registry; return registry; } // 注册init函数 void register_init(std::function<void()> init_func) { init_handlers_.emplace_back(std::move(init_func)); } // 注册kill函数(绑定子类实例的kill方法) void register_kill(std::function<void(int)> kill_func) { kill_handlers_.emplace_back(std::move(kill_func)); } // 统一调用所有init void init_all() { for (auto& func : init_handlers_) func(); } // 统一调用所有kill void kill_all(int id) { for (auto& func : kill_handlers_) func(id); } private: HandlerRegistry() = default; ~HandlerRegistry() = default; HandlerRegistry(const HandlerRegistry&) = delete; HandlerRegistry& operator=(const HandlerRegistry&) = delete; std::vector<std::function<void()>> init_handlers_; std::vector<std::function<void(int)>> kill_handlers_; }; // CRTP注册器模板:子类继承后自动完成注册 template<EventHandler T> class AutoRegistrar { protected: AutoRegistrar() { HandlerRegistry::instance().register_init(&T::init); HandlerRegistry::instance().register_kill([](int id) { T{}.kill(id); // 这里假设子类默认构造函数可用,若不可用可调整为其他实例化方式 }); } // 确保子类继承后不会被切片 ~AutoRegistrar() = default; }; }
3. 事件子类的写法(自动注册)
只需要让子类继承AutoRegistrar<T>,就能自动完成注册,新增子类无需修改调用逻辑:
class A : public event_detail::AutoRegistrar<A> { public: static void init() { // 你的初始化逻辑,比如订阅初始化 std::cout << "A initialized\n"; } void kill(int id) { // 你的实体删除逻辑 std::cout << "A killed, ID: " << id << "\n"; } }; class B : public event_detail::AutoRegistrar<B> { public: static void init() { std::cout << "B initialized\n"; } void kill(int id) { std::cout << "B killed, ID: " << id << "\n"; } }; // 新增子类C时,只需要写类定义,不需要在任何地方手动调用C::init() class C : public event_detail::AutoRegistrar<C> { public: static void init() { std::cout << "C initialized\n"; } void kill(int id) { std::cout << "C killed, ID: " << id << "\n"; } };
4. 统一调用入口
在程序启动/结束时,只需要调用注册器的统一方法即可:
int main() { // 初始化所有事件子类 event_detail::HandlerRegistry::instance().init_all(); // 业务逻辑... // 销毁所有事件子类(传入ID) event_detail::HandlerRegistry::instance().kill_all(100); return 0; }
二、constexpr子类列表与编译期初始化
如果需要实现编译期constexpr的子类列表,并在编译期完成初始化,我们可以结合模板元编程的TypeList和折叠表达式实现:
1. 定义编译期类型列表
namespace event_detail { // 编译期类型列表模板 template<typename... Ts> struct TypeList {}; // 遍历TypeList,调用每个类型的constexpr init() template<typename... Ts> constexpr void constexpr_init_all(TypeList<Ts...>) { (Ts::init(), ...); // C++17折叠表达式,编译期依次调用每个子类的init } }
2. 构建constexpr子类列表
你可以手动维护这个列表,或者用宏简化新增子类的添加:
// 手动定义constexpr子类列表 using ConstexprHandlerList = event_detail::TypeList<A, B, C>; // 编译期自动调用所有init constexpr auto dummy_init = [](){ event_detail::constexpr_init_all(ConstexprHandlerList{}); return 0; }();
注意事项
- 编译期初始化要求
init()必须是constexpr函数,且内部逻辑符合constexpr规则(不能有动态内存分配、非constexpr函数调用等)。 - 编译期列表无法自动收集运行时注册的子类,所以如果需要完全自动,可能需要结合编译器扩展(比如GCC的
__attribute__((constructor))),但这会失去constexpr特性。
三、额外优化与注意点
- 依赖顺序控制:如果子类的
init()有依赖关系,可以给注册器添加优先级参数,在统一调用时按优先级排序执行。 - 实例化方式调整:如果子类无法默认构造,可以在注册
kill函数时,改用全局实例或者自定义实例化逻辑。 - 宏替代继承:如果不想用CRTP继承,也可以用宏来生成静态注册器变量,比如:
然后在子类定义后添加#define REGISTER_EVENT_HANDLER(T) \ static event_detail::Registrar<T> _registrar_##T;REGISTER_EVENT_HANDLER(A)即可。
内容的提问来源于stack exchange,提问作者Khlorghaal
相关产品推荐
相关产品推荐

