商城首页欢迎来到中国正版软件门户

您的位置:首页 >C++实现观察者模式及事件回调示例

C++实现观察者模式及事件回调示例

  发布于2026-02-28 阅读(0)

扫一扫,手机访问

C++观察者模式推荐用std::vector<std::function<...>>存储回调,避免裸指针和shared_ptr基类;注册时谨慎捕获变量,通知时浅拷贝容器防迭代器失效;提供sync/async双接口,异步需复制回调;手写比signal/slot库更轻量可控。

C++如何实现观察者模式?(事件回调机制示例)

std::function + std::vector 怎么存回调函数

观察者模式的核心是“一堆对象等着被通知”,C++里最轻量、最可控的做法就是用 std::vectorstd::function。别碰裸指针或 std::shared_ptr 包装的抽象基类——除非你真需要运行时多态解耦,否则纯属增加生命周期管理负担。

常见错误是把 std::function 当成普通函数指针用,结果捕获了局部变量却没检查生命周期:[&] 在异步通知中大概率导致崩溃;[=] 捕获值又容易忽略对象是否可拷贝。

  • 只捕获必要项,优先用 [this][ptr = shared_from_this()](如果用了 std::enable_shared_from_this
  • 注册回调时,建议用返回值标识订阅句柄(比如 size_t 索引或 int64_t token),方便后续移除
  • 避免在回调执行中途修改容器——std::vectorerase 会失效迭代器,通知循环得用下标或先收集待删索引

notify() 里怎么安全遍历并调用观察者

最典型的坑是:一边通知一边有观察者自己调用 unsubscribe(),导致迭代器失效或访问野指针。C++ 没有内置“快照式遍历”,得手动处理。

不是所有场景都需要深拷贝回调列表——如果通知是同步的、且明确禁止在回调里改订阅关系,那直接遍历原容器就行;但只要有一丁点可能(比如日志观察者写磁盘慢、触发重试逻辑),就必须隔离。

  • 推荐做法:进入 notify() 后,用 auto observers = m_observers; 浅拷贝一份 std::vector<std::function<...>>,再遍历它
  • 浅拷贝开销小(std::function 内部是小对象优化+指针),比锁 + 迭代器安全方案更简单可靠
  • 如果观察者数量极大(比如上万)、且通知极频繁,才考虑用 std::shared_ptr<const std::vector<...>> 配合原子指针交换,但绝大多数业务用不到

如何支持跨线程通知(不崩、不丢、不卡)

观察者模式默认是同步调用,一旦某个回调阻塞或抛异常,整个通知链就停住。跨线程不是加个 std::thread 就行——核心矛盾是:谁负责生命周期?谁处理异常?消息顺序还保不保?

别用全局队列+单消费者线程来“统一派发”,这会让调用栈不可控、调试困难。真正合理的做法是让事件发布者决定分发策略。

  • 提供两个通知接口:notify_sync()(当前线程立即调用)和 notify_async()(用 std::async(std::launch::async, ...) 或线程池投递)
  • notify_async() 内部必须复制回调对象(即再次浅拷贝),因为原始 m_observers 可能被其他线程修改
  • 异步回调里别 throw 异常——std::async 不传播异常到发布者,得用 std::future::wait() 主动取,但通常没人这么干;更实际的是记录日志 + 继续下一个

为什么不用 signal/slot 库(如 libsigc++、Boost.Signals2)

这些库确实封装了连接管理、自动断连、线程安全等,但代价是:二进制体积增大、编译时间变长、调用栈更深、部分实现依赖 RTTI 或异常。一个只有几十行的 Subject 类,往往比引入整个 Boost 更可靠。

它们真正的适用场景是:项目已重度使用该生态、需要信号合并/限流/延迟触发等高级特性,或者团队对自研机制缺乏信心。但如果你只是想让 UI 控件响应数据变化,或模块间松耦合通信,手写更透明、更容易 debug。

  • Boost.Signals2 的 scoped_connection 看似优雅,但若观察者对象析构顺序不对,仍可能调用已释放内存
  • libsigc++ 的模板深度高,错误信息极其晦涩,比如 sigc::slot 类型不匹配时,编译器报错常跨越百行
  • 手写方案里,每个 unsubscribe() 就是简单的 vector.erase(),哪行崩了一眼就能定位

复杂点永远在生命周期管理——不是语法难,而是你得清楚谁 new 谁 delete、谁 hold 引用、谁负责在析构前清理订阅。这点,任何库都不会替你做决定。

本文转载于:互联网 如有侵犯,请联系zhengruancom@outlook.com删除。
免责声明:正软商城发布此文仅为传递信息,不代表正软商城认同其观点或证实其描述。

热门关注