RAII封装ObserverHandle实现自动注册与注销,结合无锁延迟回调队列、模板化type_index事件分发、scoped_connection一次性通知及静态全局观察者池,构成零开销、线程安全、类型安全的C++观察者模式方案。

一、基于RAII的观察者注册与自动注销机制
在C++中,观察者模式常因手动管理订阅生命周期导致悬挂指针或重复注销问题。RAII风格封装将观察者句柄与作用域绑定,确保离开作用域时自动从被观察者中移除回调,无需显式调用注销接口。
1、定义一个轻量级ObserverHandle类,内部持有被观察者对象的弱引用(如std::weak_ptr)及唯一注册ID;
2、ObserverHandle的构造函数接收被观察者可变引用(如std::shared_ptr
3、ObserverHandle的析构函数检查weak_ptr是否仍有效,若有效则调用subject->unsubscribe(id)执行安全注销;
4、禁用拷贝构造与拷贝赋值,仅允许移动语义,防止同一回调被多次管理;
5、提供explicit operator bool()用于判断句柄是否处于有效注册状态,该操作不抛出异常且线程安全。
二、无锁单线程回调队列+延迟执行封装
为避免回调执行期间被观察者状态突变引发竞态,采用“注册即入队、执行延后”的策略:所有通知不直接调用用户回调,而是推入局部栈或对象内嵌队列,在明确可控时机(如事件循环tick、作用域末尾)统一派发。
1、在Subject类中声明std::vector<:function>> m_pending_callbacks成员;
2、notify()方法不遍历观察者列表,而是将每个已注册回调的包装lambda(捕获当前状态快照)push_back至m_pending_callbacks;
3、引入flush_pending()公有方法,按FIFO顺序遍历并清空m_pending_callbacks,逐个执行;
4、在关键临界区末尾(如锁释放后、函数返回前)显式调用flush_pending();
5、flush_pending()必须保证无重入性,内部使用std::exchange(m_pending_callbacks, {})避免迭代中修改。
三、模板化类型擦除回调存储(std::any + type_id校验)
支持异构事件类型通知,避免为每种事件定义独立Subject子类。通过编译期type_info与运行时std::any组合,实现单一Subject实例承载多种事件签名的观察者注册。
1、定义using EventKey = std::type_index;注册时以typeid(T).hash_code()作为事件键;
2、Subject内部使用std::unordered_map
3、subscribe模板函数接收std::function
4、notify模板函数根据T类型查找对应桶,对每个std::any对象执行std::any_cast<:function t>>并调用;
5、每次cast前必须校验std::any.type() == typeid(std::function
四、scoped_connection仿函数对象封装
提供类似Boost.Signals2的scoped_connection语义:将连接句柄封装为可调用对象,其operator()直接触发一次通知,同时隐含自动断开逻辑(仅限一次性回调场景)。
1、定义scoped_notify类,模板参数为事件类型T,私有成员包含std::shared_ptr
2、构造函数完成注册,并设置内部标志位m_fired = false;
3、重载operator(),检查m_fired,若为false则调用Subject::notify_once
4、析构函数中若m_fired为false,则执行unsubscribe操作;
5、operator()为const成员函数,但内部使用mutable bool标记状态,符合C++17 mutable语义规范。
五、静态全局观察者池+编译期注册表
适用于配置驱动型系统,要求所有观察者在main()开始前完成注册,且不依赖动态内存分配。利用函数局部静态变量与初始化顺序特性构建零开销注册表。
1、声明全局constexpr size_t MAX_OBSERVERS = 64;定义std::array<:pair void>, MAX_OBSERVERS> g_observer_pool;
2、每个观察者模块提供宏OBSERVER_REGISTER(name, func, data),生成一个局部静态结构体,在其构造函数中将{func, data}写入g_observer_pool首个空位;
3、Subject::notify_all()遍历g_observer_pool非空项,以data为参数调用func;
4、使用std::atomic_size_t索引计数器保证多线程首次注册安全;
5、所有注册行为发生在DLL加载或main()之前,无堆分配、无虚函数、无RTTI开销。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











