您的位置:首页 >C++实现观察者模式及事件回调示例
发布于2026-02-28 阅读(0)
扫一扫,手机访问
C++观察者模式推荐用std::vector<std::function<...>>存储回调,避免裸指针和shared_ptr基类;注册时谨慎捕获变量,通知时浅拷贝容器防迭代器失效;提供sync/async双接口,异步需复制回调;手写比signal/slot库更轻量可控。

观察者模式的核心是“一堆对象等着被通知”,C++里最轻量、最可控的做法就是用 std::vector 存 std::function。别碰裸指针或 std::shared_ptr 包装的抽象基类——除非你真需要运行时多态解耦,否则纯属增加生命周期管理负担。
常见错误是把 std::function 当成普通函数指针用,结果捕获了局部变量却没检查生命周期:[&] 在异步通知中大概率导致崩溃;[=] 捕获值又容易忽略对象是否可拷贝。
[this] 或 [ptr = shared_from_this()](如果用了 std::enable_shared_from_this)size_t 索引或 int64_t token),方便后续移除std::vector 的 erase 会失效迭代器,通知循环得用下标或先收集待删索引最典型的坑是:一边通知一边有观察者自己调用 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 可能被其他线程修改std::async 不传播异常到发布者,得用 std::future::wait() 主动取,但通常没人这么干;更实际的是记录日志 + 继续下一个这些库确实封装了连接管理、自动断连、线程安全等,但代价是:二进制体积增大、编译时间变长、调用栈更深、部分实现依赖 RTTI 或异常。一个只有几十行的 Subject 类,往往比引入整个 Boost 更可靠。
它们真正的适用场景是:项目已重度使用该生态、需要信号合并/限流/延迟触发等高级特性,或者团队对自研机制缺乏信心。但如果你只是想让 UI 控件响应数据变化,或模块间松耦合通信,手写更透明、更容易 debug。
scoped_connection 看似优雅,但若观察者对象析构顺序不对,仍可能调用已释放内存sigc::slot 类型不匹配时,编译器报错常跨越百行unsubscribe() 就是简单的 vector.erase(),哪行崩了一眼就能定位复杂点永远在生命周期管理——不是语法难,而是你得清楚谁 new 谁 delete、谁 hold 引用、谁负责在析构前清理订阅。这点,任何库都不会替你做决定。
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
9