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

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特性。

三、额外优化与注意点

  1. 依赖顺序控制:如果子类的init()有依赖关系,可以给注册器添加优先级参数,在统一调用时按优先级排序执行。
  2. 实例化方式调整:如果子类无法默认构造,可以在注册kill函数时,改用全局实例或者自定义实例化逻辑。
  3. 宏替代继承:如果不想用CRTP继承,也可以用宏来生成静态注册器变量,比如:
    #define REGISTER_EVENT_HANDLER(T) \
        static event_detail::Registrar<T> _registrar_##T;
    
    然后在子类定义后添加REGISTER_EVENT_HANDLER(A)即可。

内容的提问来源于stack exchange,提问作者Khlorghaal

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 03:51:54