observer_handle必须是move-only类型,以确保“订阅-退订”一对一绑定;拷贝构造/赋值需显式删除,移动后原句柄置空,防止重复erase或悬空引用。
直接用
+
管理回调,不加 RAII 封装,基本等于裸写——生命周期错配、重复注册、野指针调用全靠人肉防。真要轻量又安全,必须把「订阅」和「自动退订」绑死在对象生命周期上。
为什么 observer_handle 必须是 move-only 类型
避免同一回调被多个 handle 持有,导致析构时重复 erase 或悬空引用。copyable 会破坏 RAII 的“一对一绑定”契约。
的拷贝构造/赋值运算符必须显式删除(
)
move 构造后原 handle 置为无效态(如内部指针设为
),防止二次析构
若允许 copy,
可能两次调用
同一迭代器,UB
callback_list 容器选 std::vector 还是 std::list
选
更合适,但关键不是容器类型,而是怎么存迭代器/句柄。
存
不可行:插入/删除会失效;改用下标索引(
)更稳
每个回调注册时分配唯一
(如原子递增计数器),handle 持有该
,注销时按 id 查找并 swap-erase
虽迭代器稳定,但缓存不友好、内存开销大,对轻量级目标得不偿失
如何避免 notify 期间修改容器导致的迭代器失效
不能边遍历边
,也不能在回调里调用
/
。
C知道
CSDN推出的一款AI技术问答工具
下载
立即学习
“
C++免费学习笔记(深入)
”;
notify 前先拷贝一份活跃回调的
(只读快照)
回调执行全程不持有任何锁或引用原始容器,彻底隔离修改与通知
若需支持运行时动态增删,用双缓冲:当前 notify 用 buffer A,新增/删除操作进 buffer B,notify 结束后 swap
错误现象示例:
在回调里触发 erase → it 失效 → 下次 ++ UB
RAII handle 的典型误用场景
handle 生命周期脱离了预期作用域,是最隐蔽的坑。
把 handle 存入全局
或静态容器 → 对象析构后 handle 仍活着,析构时 erase 已销毁的 vector
lambda 捕获了局部对象的引用,handle 存活但 lambda 捕获失效 → notify 时访问 dangling reference
跨线程传递 handle:未加同步就在线程 A 构造、线程 B 析构 → data race on internal id/index
正确做法:handle 必须和观察者对象同生共死;若需跨作用域,用
管理观察者,handle 持有 weak_ptr 防循环引用
最易被忽略的是 notify 快照的拷贝成本——如果回调函数体本身很重,拷贝
对象可能比执行还慢;这时候得权衡:要么接受小对象拷贝,要么改用回调对象地址+虚函数表方案,但那就不是“轻量级”了。
std::functionstd::vectorobserver_handle= deletenullptr~observer_handle()erasestd::vector<:function>> std::vector::iteratorsize_tididstd::listerasesubscribeunsubscribestd::vector<:function>>std::vector::erase(it++)std::vectorstd::shared_ptrstd::function