observer_handle必须是move-only类型,以确保“订阅-退订”一对一绑定;拷贝构造/赋值需显式删除,移动后原句柄置空,防止重复erase或悬空引用。

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

















