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

技术百科 裘德小鎮的故事 发布时间:2026-01-26 浏览:
可用std::function+std::vector实现轻量观察者模式:用std::function封装回调避免生命周期问题,vector存储观察者,通知时遍历调用;需防循环引用(weak_ptr)、禁在通知中增删、无std::signal/std::slot标准组件。

用 std::function + std::vector 实现轻量级观察者注册与通知

不需要引入 Boost 或 Qt,C++11 起就能靠 std::functionstd::vector 搭出可用的观察者链。核心是把回调抽象成类型擦除后的可调用对象,避免模板爆炸或基类继承耦合。

常见错误是直接存裸函数指针或 lambda(带捕获)导致生命周期失控——必须用 std::function 统一封装,且注册方需确保回调对象比被观察者活得久,否则通知时会 crash。

  • 定义观察者类型:using Observer = std::function;Event 可为 struct 或 void)
  • 在被观察者类中维护:std::vector observers_;
  • 注册用 void attach(Observer obs),直接 push_back;移除建议用 erase + remove_if 配合标识符(如 ID 或 weak_ptr 包装),不推荐裸 erase 迭代器(通知中修改容器易崩溃)
  • 通知时用范围 for 循环遍历调用,**不要在通知过程中增删 observer**(可先拷贝一份 observers_ 再遍历)

如何避免循环引用:shared_ptr/weak_ptr 在观察者中的正确用法

当观察者是某个对象的成员函数(如 obj->onDataReady),用 std::bind 或 lambda 捕获 this 会导致被观察者持有了对观察者的强引用,而观察者又可能间接持有被观察者,形成循环引用、内存泄漏。

解决方案是让被观察者只存 std::weak_ptr,通知前 lock 判断是否还存活:

  • 观察者注册时传入 std::shared_ptr 对象和成员函数指针:auto self = shared

    _from_this(); attach([self](const Event& e) { if (self) self->handle(e); });
  • 或在被观察者内部用 std::vector<:weak_ptr>> 存储,每次通知前 if (auto p = wp.lock()) p->update(e);
  • Qt 的 QObject::connect 默认做类似检查,但纯 C++ 需手动保障

std::signal/std::slot 不是标准库组件:别被名字误导

C++ 标准库 **没有** std::signalstd::slotstd::connection ——这些是 Boost.Signals2 或 Qt 的命名。误以为它们是标准特性,会导致编译失败或查文档走弯路。

如果项目允许第三方依赖,Boost.Signals2 提供线程安全、自动断连、序列化支持;Qt 的 QSignalMapper 和新语法 connect(obj, &Class::sig, this, &Class::slot) 更适合 GUI 场景。但若追求零依赖,手写上面两个方案更可控。

  • Boost.Signals2 中连接返回 boost::signals2::connection,可调用 disconnect() 或作用域结束自动断开
  • Qt 的信号槽在跨线程时需指定 Qt::QueuedConnection,否则默认直连(同线程)可能破坏封装
  • 手写方案里,「自动断连」只能靠 weak_ptr 或显式 detach(),没有魔法

事件参数传递:值拷贝 vs const 引用 vs std::any

通知时传什么类型,直接影响性能和灵活性。传大对象值拷贝(void notify(Event e))可能引发多次构造;全用 const Event& 又要求所有事件继承同一基类,增加耦合。

折中做法是分层设计:

  • 简单场景:统一用 struct Event { enum Type; std::any data; };,发送方塞任意值,接收方 std::any_cast 判断类型——但运行时开销和类型安全风险需权衡
  • 中等规模:用 variant(C++17)替代 any:std::variant,编译期检查 + 零分配
  • 高频通知(如游戏帧更新):避免任何动态分配,传裸指针 + 类型 ID,或用 tag dispatch + 函数重载

最容易被忽略的是 const 正确性:如果事件对象在通知后还会被修改(比如异步队列中复用),那传 const 引用反而危险——此时必须值拷贝或移动语义。


# 的是  # 就能  # 还会  # 不需要  # 第三方  # 又要  # app  # auto  # 循环  # 对象  # 走弯路  # c++  # if  # void  # class  # 函数重载  # 标准库  # 指针  # 线程  # signal  # function  # 异步  # 事件  # red  # Event  # this  # 回调  # 封装  # 成员函数  # 继承  # 作用域  # Struct  # 遍历  # for  # enum  # Lambda  # 标识符  # const  # using  # qt  # 活得 


相关栏目: <?muma $count = M('archives')->where(['typeid'=>$field['id']])->count(); ?> 【 AI推广<?muma echo $count; ?> 】 <?muma $count = M('archives')->where(['typeid'=>$field['id']])->count(); ?> 【 SEO优化<?muma echo $count; ?> 】 <?muma $count = M('archives')->where(['typeid'=>$field['id']])->count(); ?> 【 技术百科<?muma echo $count; ?> 】 <?muma $count = M('archives')->where(['typeid'=>$field['id']])->count(); ?> 【 谷歌推广<?muma echo $count; ?> 】 <?muma $count = M('archives')->where(['typeid'=>$field['id']])->count(); ?> 【 百度推广<?muma echo $count; ?> 】 <?muma $count = M('archives')->where(['typeid'=>$field['id']])->count(); ?> 【 网络营销<?muma echo $count; ?> 】 <?muma $count = M('archives')->where(['typeid'=>$field['id']])->count(); ?> 【 案例网站<?muma echo $count; ?> 】 <?muma $count = M('archives')->where(['typeid'=>$field['id']])->count(); ?> 【 精选文章<?muma echo $count; ?>

相关推荐

在线咨询

点击这里给我发消息QQ客服

在线咨询

免费通话

24h咨询:4006964355


如您有问题,可以咨询我们的24H咨询电话!

免费通话

微信扫一扫

微信联系
返回顶部