观察者模式应使用std::function+std::vector存储回调,注册用lambda或bind,notify时拷贝列表并try-catch隔离异常,用weak_ptr或observertoken管理生命周期,避免shared_ptr循环引用和遍历时删除。

std::function + std::vector 怎么存回调函数
观察者模式本质是“一堆函数等着被调用”,C++里最轻量、最灵活的载体就是 std::function<void></void>(或带参数的变体)。别用裸函数指针或虚函数强制继承,那会绑死接口、增加耦合。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 用
std::vector<:function event>> m_observers</:function>存回调,Event 是你定义的事件结构体,按需传参 - 注册时直接用 lambda、成员函数绑定(
std::bind或[this]{...}),不用额外写观察者类 - 注意:lambda 捕获局部变量时,确保被观察对象生命周期长于观察者;否则回调时访问野指针
- 避免在回调里调用
remove_observer——遍历 vector 时删元素会崩,改用“标记+延迟清理”或erase-remove惯用法
notify() 里怎么安全触发所有回调
常见错误是边遍历边修改容器,或者没处理回调抛异常导致整个通知链中断。C++没内置“异常隔离”,得自己兜底。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 先拷贝一份 observer 列表:
auto observers = m_observers,再遍历它,这样增删不影响当前通知 - 每个回调外层套
try { ... } catch (...) { /* 记日志,不 throw */ },防止一个挂掉全军覆没 - 不要在 notify 里锁全局 mutex——如果回调本身又去等其他锁,极易死锁;真要线程安全,用
std::shared_mutex读多写少场景,或把同步逻辑交给调用方
怎么解耦观察者和被观察者的内存管理
裸指针、std::shared_ptr 都容易出问题:shared_ptr 循环引用,裸指针释放后还调用。核心矛盾是“谁负责告诉通知系统:我死了,别再叫我”。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 用
std::weak_ptr包裹观察者对象(比如std::function<void></void>里捕获weak_ptr,调用前lock()) - 更简单粗暴但实用的做法:观察者注册时返回一个
ObserverToken(本质是size_t索引或void*),注销时传回来——由被观察者维护映射表,查表删对应项 - 禁止让被观察者持有
shared_ptr<observer></observer>,这等于强迫观察者必须用shared_ptr构造,侵入性太强
std::signal/std::slot 库值不值得引入
像 boost::signals2 或 Qt 的 QObject::connect 确实封装了自动断连、线程安全、序列化等,但代价是二进制体积、编译时间、学习成本。90% 的内部工具或游戏逻辑根本用不到那么重的功能。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 项目已用 Qt?直接用
QSignalMapper或新式 connect,别重复造轮子 - 纯 C++17 项目且需要跨线程自动队列?考虑
libsigc++或手写一个基于std::queue+std::condition_variable的异步分发器 - 否则,坚持手写 50 行以内的
Subject类——越简单,越容易定位回调不触发、重复注册、析构顺序错这类问题
真正难的不是写 notify,是搞清谁该在什么时候注销、回调里能不能 delete this、多线程下 event 对象该谁 delete。这些细节不画图、不跑 ASan,光看代码很难发现。










