C++怎么实现观察者模式 C++事件通知机制设计与实现【设计模式】

2026-01-29 00:00:00 作者:裘德小鎮的故事
观察者模式核心是解决谁通知谁、何时解绑、生命周期管理三问题;需用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::remov

    e_if
    + lock(),因为 lock() 可能抛异常(虽然通常不会),且语义不如显式遍历清晰

事件参数类型要支持值语义或移动语义

通知时传参别用 const T& 引用——如果事件源对象在通知中途析构,引用就悬空了。尤其在多线程下,这个坑非常隐蔽。

  • 简单类型(intstd::string)直接按值传递
  • 大对象优先用 std::unique_ptr 或移动构造,避免拷贝开销
  • 如果必须共享数据,用 std::shared_ptr,确保只读且生命周期可控

例如:

void notify(std::string event_name, std::shared_ptr data);
// 而不是 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 的坑。

猜你喜欢

联络方式:

400 9058 355

邮箱:8955556@qq.com

Q Q:8955556

微信二维码
在线咨询 拨打电话

电话

400 9058 355

微信二维码

微信二维码