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::function 和 std::vector 搭出可用的观察者链。核心是把回调抽象成类型擦除后的可调用对象,避免模板爆炸或基类继承耦合。
常见错误是直接存裸函数指针或 lambda(带捕获)导致生命周期失控——必须用 std::function 统一封装,且注册方需确保回调对象比被观察者活得久,否则通知时会 crash。
- 定义观察者类型:
using Observer = std::function(; Event可为 struct 或 void) - 在被观察者类中维护:
std::vectorobservers_; - 注册用
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::signal、std::slot 或 std::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; ?>
】
相关推荐
- Win10电脑C盘红了怎么清理_Windows10
- Windows10如何更改桌面图标间距_Win10
- Windows10系统怎么查看CPU温度_Win1
- Python包结构设计_大型项目组织解析【指导】
- Win11无法识别耳机怎么办_解决Win11插耳机
- Win11怎么更改任务栏颜色_Windows11个
- 如何使用Golang recover捕获panic
- 如何从 Go 的 map[string]inter
- Win10怎么卸载迅雷_Win10彻底卸载迅雷方法
- Win11用户账户控制怎么关_Win11关闭UAC
- Win11怎样安装剪映专业版_Win11安装剪映教
- ACF 教程:如何正确更新嵌套在多层 Group
- Python对象比较排序规则_集合使用说明【指导】
- MAC怎么使用表情符号面板_MAC Emoji快捷
- Windows怎样关闭Edge新标签页广告_Win
- c++怎么实现高并发下的无锁队列_c++ std:
- Windows电脑键盘突然失灵怎么办?(驱动与硬件
- Windows10系统怎么查看系统版本_Win10
- 如何在 IIS 上为 ASP.NET 6 应用排除
- Python项目回滚策略_发布安全说明【指导】
- 如何在 Go 中正确测试带 Cookie 的 HT
- C++如何使用std::async进行异步编程?(
- Win11怎么设置系统还原_Windows11系统
- C++ static_cast和dynamic_c
- Win11讲述人怎么关闭_Win11误触开启语音朗
- 如何在Golang中操作嵌套切片指针_Golang
- php与c语言在嵌入式中有何区别_对比两者在硬件控
- Win11怎么设置屏保时间_调整Win11屏幕保护
- Win10怎样设置闹钟贪睡时间 Win10闹钟贪睡
- c++如何使用std::bind绑定函数参数_c+
- Win11怎么关闭系统推荐内容_Windows11
- MAC怎么一键隐藏桌面所有图标_MAC极简模式切换
- 如何在Golang中处理云原生事件_使用Event
- Python路径拼接规范_跨平台处理说明【指导】
- Win11如何设置开机问候语 Win11修改登录界
- Bpmn 2.0的XML文件怎么画流程图
- Win11怎么设置桌面图标间距_Windows11
- Windows10如何更改盘符名称_Win10重命
- Win11怎么关闭防火墙通知_屏蔽Win11安全中
- Win11怎样安装网易云音乐_Win11安装网易云
- 如何使用Golang defer优化性能_减少不必
- 如何使用Golang读取日志文件_Golang b
- Win11截图快捷键是什么_Win11自带截图工具
- 如何使用Golang实现Web表单数据绑定_自动映
- Win10电脑怎么设置休眠快捷键_Windows1
- Win11任务栏天气怎么关闭 Win11隐藏天气小
- 如何使用Golang管理跨项目依赖_Golang多
- Python对象生命周期管理_创建销毁说明【指导】
- Win11怎么更改系统语言_Win11中文语言包下
- php报错怎么查看_定位PHP致命错误与警告的方法


QQ客服