观察者对象析构时自动移除的核心解法是:被观察者持有std::weak_ptr<Observer>,观察者继承enable_shared_from_this并注册shared_from_this();notify时对每个weak_ptr调用lock(),仅执行非空结果。

观察者对象析构时如何自动从被观察者中移除
观察者模式里最常踩的坑,就是观察者对象销毁了,但没从被观察者列表里删掉,导致后续通知时调用已释放内存——Segmentation fault 或 use-after-free。C++ 没有垃圾回收,必须手动解注册,但“手动”容易漏。核心解法是:把注册和生命周期绑定,让观察者自己负责注销。
推荐做法是让被观察者持有 std::weak_ptr<Observer>,而非 std::shared_ptr<Observer> 或裸指针。这样观察者析构后,weak_ptr.lock() 返回空,通知前可安全跳过。
- 被观察者维护
std::vector<std::weak_ptr<Observer>>,不是std::vector<Observer*> - 观察者继承自
std::enable_shared_from_this<Observer>,确保能生成有效的shared_ptr - 注册时传入
shared_from_this(),被观察者存其weak_ptr - 每次通知前,对每个
weak_ptr调用lock(),仅对非空结果调用回调
为什么不能用 shared_ptr 直接管理观察者生命周期
std::shared_ptr 看似方便,但会延长观察者寿命——只要被观察者还持有它,观察者就无法析构。这违背“观察者应能随时销毁”的设计意图,尤其在 UI 组件、临时监听器等场景下极易引发循环引用或资源滞留。
典型错误写法:subject.register_observer(std::make_shared<MyObserver>())。此时即使 MyObserver 本该随局部作用域结束,也会因被 subject 持有而继续存活。
立即学习“C++免费学习笔记(深入)”;
- 若观察者需被多个被观察者监听,
shared_ptr会让生命周期完全失控 -
weak_ptr不影响引用计数,只提供“尝试访问”能力,语义更干净 - 配合
enable_shared_from_this是唯一安全获取自身shared_ptr的方式;直接shared_ptr<this>是未定义行为
通知循环中 erase-remove 惯用法失效怎么办
有人想在通知前遍历并 erase 失效的 weak_ptr,但边遍历边修改容器本身(尤其 vector)容易出错,且没必要——清理可延迟到下次通知前,或由观察者主动注销触发。
更轻量的做法是:不主动清理,只在 notify() 中过滤。性能上几乎无损,因为 weak_ptr::lock() 是原子读+引用计数检查,开销极小。
- 不要在
notify()内部做erase——迭代器失效风险高,且重复清理浪费 - 如需定期清理(比如长期运行服务),可在空闲时调用一次
remove_if+erase,用[](const auto& wp) { return wp.expired(); } - 若观察者支持显式注销(如
unregister()),应在注销时立即从容器中移除对应weak_ptr,避免积压
多线程环境下 weak_ptr.lock() 是否线程安全
是的。std::weak_ptr::lock() 是线程安全的,标准保证其原子性:要么返回有效 shared_ptr,要么返回空,不会出现中间状态。但注意,返回的 shared_ptr 只保证“构造完成”,后续对其所指对象的访问仍需同步。
- 被观察者可无锁调用
lock(),无需额外互斥 - 但观察者对象内部状态(如成员变量)若被多线程并发修改,仍需加锁或使用原子类型
- 避免在
lock()返回的shared_ptr上调用非线程安全的成员函数——这不是weak_ptr的责任,而是观察者自身的设计问题
真正难处理的是跨线程注销:一个线程正在 notify(),另一个线程却刚销毁观察者。这时 weak_ptr::lock() 自动兜底,比任何手动标记+锁方案都可靠。别试图用标志位+双重检查,weak_ptr 就是为此而生的。


















