观察者模式核心是解决谁通知谁、何时解绑、生命周期管理三问题;需用std::weak_ptr避免野指针,通知时分离列表变更,参数按值或移动传递防悬空。

观察者模式的核心是避免硬编码依赖
C++里实现观察者模式,关键不是“怎么写类”,而是解决「谁通知谁、何时解绑、生命周期怎么管」这三个问题。硬写一个 Observer 基类加虚函数,很容易在对象析构后还被调用,触发野指针崩溃。
- 观察者必须能安全注销自己,不能靠「手动调用
detach()」这种易遗漏的方式 - 通知逻辑要支持异步或延迟执行(比如 UI 更新不能在数据线程直接调用)
-
std::function+std::shared_ptr组合比纯虚函数接口更灵活,也更容易和现代 C++ 工具链(如QObject信号槽、boost::signals2)对齐
用 std::weak_ptr 管理观察者生命周期
裸指针或 std::shared_ptr 都会延长观察者寿命,造成循环引用或提前释放;std::weak_ptr 是唯一能“尝试访问、失败就跳过”的方案。
class Subject {
std::vector> observers_;
public:
void attach(std::shared_ptr obs) {
observers_.push_back(obs);
}
void notify() {
for (auto it = observers_.begin(); it != observers_.end();) {
if (auto obs = it->lock()) {
obs->onEvent();
++it;
} else {
it = observers_.erase(it); // 自动清理已销毁的观察者
}
}
}
};
-
lock()返回std::shared_ptr,空则说明观察者已析构 - 遍历时用
erase()迭代器返回值,避免迭代器失效 - 不要用
std::vector::remove_if+lock(),因为lock()可能抛异常(虽然通常不会),且语义不如显式遍历清晰
事件参数类型要支持值语义或移动语义
通知时传参别用 const T& 引用——如果事件源对象在通知中途析构,引用就悬空了。尤其在多线程下,这个坑非常隐蔽。
- 简单类型(
int、std::string)直接按值传递 - 大对象优先用
std::unique_ptr或移动构造,避免拷贝开销 - 如果必须共享数据,用
std::shared_ptr,确保只读且生命周期可控
例如:
立即学习“C++免费学习笔记(深入)”;
void notify(std::string event_name, std::shared_ptrdata); // 而不是 void notify(const Data& data);
不要在通知过程中修改观察者列表
这是最常被忽略的并发与迭代陷阱。哪怕单线程,onEvent() 内部调用 subject.detach(this),也会导致当前 notify() 循环中迭代器失效。
-
解决方法:先收集待移除的
weak_ptr,再统一清理 - 更稳妥的做法是把「变更观察者列表」推迟到通知结束后,比如用一个
std::vector<:function>>缓存回调,在notify()尾部执行 - Qt 的
QMetaObject::invokeMethod(..., Qt::QueuedConnection)就是这个思路的工程化实现
实际项目里,越早引入 std::weak_ptr 和「通知/变更分离」设计,后期越不容易掉进析构期 crash 的坑。









