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::remove_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 的坑。


# 编码  # 工具  # c++  # 解决方法  # red  # qt  # String  # const  # int  # void  # 循环  # 指针  # 虚函数  # 纯虚函数  # 接口  # 线程  # 多线程  # 值传递  # 并发  # function  # 对象  # 事件  # this  # 异步  # ui  # 迭代  # 遍历  # 这是  # 也会  # 很容易  # 能在  # 不容易  # 这三个  # 不要用 


相关栏目: 【 网站优化151355 】 【 网络推广146373 】 【 网络技术251813 】 【 AI营销90571


相关推荐: html如何与html链接_实现多个HTML页面互相链接【互相】  Laravel如何实现邮箱地址验证功能_Laravel邮件验证流程与配置  如何快速重置建站主机并恢复默认配置?  怎么制作网站设计模板图片,有电商商品详情页面的免费模板素材网站推荐吗?  网站制作免费,什么网站能看正片电影?  Android仿QQ列表左滑删除操作  php json中文编码为null的解决办法  Laravel如何实现密码重置功能_Laravel密码找回与重置流程  Laravel控制器是什么_Laravel MVC架构中Controller的作用与实践  网站制作公司哪里好做,成都网站制作公司哪家做得比较好,更正规?  如何在Ubuntu系统下快速搭建WordPress个人网站?  详解MySQL数据库的安装与密码配置  南京网站制作费用,南京远驱官方网站?  如何选择可靠的免备案建站服务器?  Laravel如何安装Breeze扩展包_Laravel用户注册登录功能快速实现【流程】  微信小程序 canvas开发实例及注意事项  中山网站推广排名,中山信息港登录入口?  公司门户网站制作公司有哪些,怎样使用wordpress制作一个企业网站?  如何在建站宝盒中设置产品搜索功能?  HTML5段落标签p和br怎么选_文本排版常用标签对比【解答】  iOS UIView常见属性方法小结  DeepSeek是免费使用的吗 DeepSeek收费模式与Pro版本功能详解  如何在阿里云香港服务器快速搭建网站?  如何自定义建站之星模板颜色并下载新样式?  javascript中闭包概念与用法深入理解  Laravel如何使用Vite进行前端资源打包?(配置示例)  如何快速建站并高效导出源代码?  利用JavaScript实现拖拽改变元素大小  Laravel项目怎么部署到Linux_Laravel Nginx配置详解  如何在阿里云购买域名并搭建网站?  Midjourney怎么调整光影效果_Midjourney光影调整方法【指南】  网站制作壁纸教程视频,电脑壁纸网站?  为什么php本地部署后css不生效_静态资源加载失败修复技巧【技巧】  Laravel如何创建自定义Facades?(详细步骤)  用v-html解决Vue.js渲染中html标签不被解析的问题  Laravel怎么使用Intervention Image库处理图片上传和缩放  如何快速使用云服务器搭建个人网站?  Laravel如何记录日志_Laravel Logging系统配置与自定义日志通道  简历在线制作网站免费版,如何创建个人简历?  如何快速打造个性化非模板自助建站?  如何快速启动建站代理加盟业务?  Laravel Eloquent访问器与修改器是什么_Laravel Accessors & Mutators数据处理技巧  Laravel如何清理系统缓存命令_Laravel清除路由配置及视图缓存的方法【总结】  INTERNET浏览器怎样恢复关闭标签页_INTERNET浏览器标签恢复快捷键与方法【指南】  公司网站制作需要多少钱,找人做公司网站需要多少钱?  使用豆包 AI 辅助进行简单网页 HTML 结构设计  微信小程序 wx.uploadFile无法上传解决办法  LinuxCD持续部署教程_自动发布与回滚机制  ,交易猫的商品怎么发布到网站上去?  Laravel的路由模型绑定怎么用_Laravel Route Model Binding简化控制器逻辑