observer类应通过weak_ptr解耦生命周期,观察者继承enable_shared_from_this并由shared_ptr管理;通知支持枚举事件类型与variant载荷;避免notify中直接erase容器,改用两阶段清理。

Observer 类怎么设计才能避免内存泄漏
观察者对象如果直接持有被观察者的裸指针,或者被观察者用 std::vector 存原始指针,很容易在观察者析构后还被调用——触发野指针访问。核心是让双方通过弱引用解耦。
- 被观察者(
Subject)内部用std::vector<:weak_ptr>></:weak_ptr>存观察者,每次通知前先lock()检查是否还存活 - 观察者继承自
std::enable_shared_from_this<observer></observer>,注册时传shared_from_this()给被观察者 - 不要用
new+ 原始指针管理观察者生命周期;所有观察者必须由std::shared_ptr管理
如何支持不同事件类型的通知
硬编码一个 notify() 函数只能发一种消息,实际中常需区分“数据更新”“连接断开”“配置重载”等事件。用模板参数或枚举+variant 是更轻量的选择。
- 推荐用
enum class EventType { DataChanged, Error, ConfigReload }作为通知参数,比泛型模板更易调试、序列化和日志打点 - 如果事件载荷差异大(比如有的带
int,有的带std::string),用std::variant<int std::string std::error_code></int>封装,配合std::visit分发 - 避免把整个
Subject对象塞进回调——只传必要字段,否则容易隐式延长生命周期
std::function 回调注册 vs 继承 Observer 接口,选哪个
两种方式都能用,但语义和维护成本差别很大:继承接口适合长期稳定的模块间通信(如 GUI 控件更新),std::function 更适合临时监听(如单元测试中捕获一次状态)。
- 继承方案:定义纯虚函数
virtual void onNotify(EventType, const std::any& data) = 0,强制派生类实现,类型安全,但每个新观察者都要写新类 -
std::function方案:被观察者提供registerCallback(std::function<void const std::any>)</void>,可绑定 lambda 或普通函数,灵活但回调里捕获变量要小心this生命周期 - 混合使用更实用:主体用继承接口,调试/测试场景额外开放
std::function注册入口
notify() 调用时崩溃的常见原因
最典型的是在通知过程中,某个观察者调用了 unregister(),导致被观察者内部容器迭代器失效。这不是线程问题,单线程下也会崩。
- 不要在
notify()循环中直接erase()容器元素;改用两阶段:先收集待移除的std::weak_ptr,再统一清理 - 避免在回调里调用被观察者的任何非 const 成员函数(包括
register()),除非你明确实现了可重入保护 - 如果必须支持运行时动态增删,用
std::list<:weak_ptr>></:weak_ptr>替代vector,因为list::erase()不影响其他迭代器
观察者模式真正的难点不在结构搭建,而在生命周期边界是否清晰。哪怕只漏掉一处 weak_ptr::lock() 检查,或在一个 lambda 里隐式捕获了即将析构的对象,都会变成深夜调试时的幽灵错误。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











