c++中如何实现观察者模式_c++设计模式之观察者模式

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。

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 得莫名其妙。