Observer接口应定义为纯虚基类,update()接收Subject&参数以避免反向依赖;Subject用std::vector管理观察者,通知前lock()确保安全;亦可选用std::function+lambda简化轻量场景。

Observer 接口设计要支持多态和解耦
观察者模式的核心是让被观察者(Subject)不依赖具体观察者类型,只依赖抽象接口。C++ 中最直接的方式是定义纯虚基类 Observer,所有具体观察者继承它并实现 update()。注意:不要在基类中持有 Subject* 指针——这会引入反向依赖;应由 Subject 在通知时把自身作为参数传入,例如 virtual void update(Subject& subject) = 0;。
常见错误是让 Observer 持有 Subject 的强引用(如 std::shared_ptr),导致循环引用。正确做法是用弱引用(std::weak_ptr)或干脆不存——通知时由 Subject 主动传递必要数据(如状态值、事件枚举),而非暴露自身指针。
Subject 管理观察者列表要用 weak_ptr 防止悬挂
Subject 内部通常用容器保存观察者指针。若用 std::vector<:shared_ptr>>,当某个 Observer 对象析构时,Subject 仍持有其 shared_ptr,后续调用会触发未定义行为。解决方案是改用 std::vector<:weak_ptr>>,每次通知前先 lock():
for (auto& obs_wptr : observers_) {
if (auto obs_ptr = obs_wptr.lock()) {
obs_ptr->update(*this);
}
}
这样既避免了悬挂指针,又不需要手动维护注册/注销逻辑。但要注意:如果 Observer 析构发生在 Subject 通知过程中,lock() 返回空,自动跳过——这是预期行为,不是 bug。
立即学习“C++免费学习笔记(深入)”;
std::function + lambda 可替代传统 Observer 类
对轻量级场景(比如 UI 控件状态变更通知),不必强制写完整类。Subject 可以用 std::vector<:function std::string>> callbacks_; 存储回调,注册时直接传 lambda:
subject.on_change([](const std::string& msg) {
std::cout << "Got: " << msg << "\n";
});
优势是零继承开销、灵活捕获上下文;缺点是无法在运行时区分回调来源,也不便于统一移除(除非返回 token 或用 std::any 封装句柄)。若需取消订阅,建议配合 std::any 或返回一个 std::size_t 索引用于 remove_callback(index)。
线程安全不能靠“感觉”,得明确加锁粒度
多个线程可能同时调用 attach()、detach() 或触发 notify()。最稳妥的是给整个观察者容器加互斥锁(如 mutable std::mutex mtx_;),所有访问都 lock-guard。但要注意:在 notify() 中调用外部回调时,**必须释放锁后再调用**,否则可能引发死锁(回调里又去调 Subject 其他方法)。
- 错误写法:锁住整个 notify 循环,然后在锁内调用
obs->update() - 正确写法:先拷贝一份存活的 observer 指针列表(在锁内完成),释放锁,再遍历调用
如果 Subject 本身已是线程安全类,那观察者列表的线程安全就是它的责任——这点常被忽略,结果在多线程环境里 crash 得莫名其妙。











