Observer 应通过模板与函数对象(如 std::function)解耦,避免继承固定基类;通知时需防止迭代器失效,推荐 weak_ptr + shared_ptr 组合管理生命周期;发布订阅可复用观察者模式底层实现。

Observer 类怎么设计才不和 Subject 耦合
关键不是“实现观察者”,而是让 Observer 不依赖具体 Subject 类型。硬编码回调函数或继承特定基类,很快就会卡死在扩展上。
推荐用模板 + 函数对象解耦:把“通知动作”抽象成可调用物,而非固定接口。比如 std::function<void event></void>,这样任意类都能注册任意逻辑,无需继承 Observer 基类。
- 避免写
class MyObserver : public ObserverBase—— 一旦要监听两种事件类型,就得多重继承或改基类,得不偿失 - 用
std::function存回调时,注意捕获局部变量的生命周期;lambda 捕获this前先确认被观察者存活时间 ≥ 观察者 - 如果必须用继承(如嵌入式环境禁用 RTTI/异常/动态分配),则
Observer基类只含纯虚update(),且参数用void*或std::any,由子类自己转型 —— 否则加个新事件就得改所有派生类
Subject::notify() 调用时崩溃的常见原因
最常踩的坑是:遍历观察者容器时,某个 Observer 的回调里又调用了 detach() 或 attach(),导致迭代器失效。
别幻想“先拷贝一份列表再遍历”能彻底解决问题 —— 内存开销大,且无法解决递归通知(A 通知 B,B 又通知 C,C 又改 A 的观察者列表)。
立即学习“C++免费学习笔记(深入)”;
- 用
std::vector<:weak_ptr>></:weak_ptr>存观察者,遍历时lock()成shared_ptr,空则跳过;这样即使对方已析构也不会 crash - 通知前加标志位(如
m_notifying = true),在attach/detach中检查该标志,若正在通知则把增删操作暂存到队列,通知结束后统一处理 - 绝对不要在
notify()循环里直接erase()容器元素 —— 即使用std::list,iterator 也可能因析构 observer 导致未定义行为
std::shared_ptr 和裸指针在 observer 生命周期管理中的取舍
裸指针最轻量,但要求使用者严格保证:观察者对象寿命 ≥ Subject;shared_ptr 自动管理,却可能引入循环引用(Subject 持有 Observer 的 shared_ptr,Observer 又持有 Subject 的 shared_ptr)。
真实项目中,90% 场景更适合 weak_ptr + shared_ptr 组合:Subject 持 std::vector<:weak_ptr>></:weak_ptr>,Observer 本身由外部用 shared_ptr 管理。
- 裸指针只适用于栈对象或全局对象作 observer,且你能 100% 控制析构顺序(比如 GUI 控件生命周期由框架管理,你只负责在
onDestroy()里手动detach) - 用
shared_ptr直接存 observer?小心循环引用 —— 改用weak_ptr在 Subject 侧,observer 内部用普通shared_ptr持 Subject 即可破环 - 如果 observer 是纯函数对象(lambda /
std::function),根本不需要指针管理 —— 它们按值存储,析构由容器自动处理
发布订阅和观察者模式在 C++ 里到底要不要分两套实现
没必要。所谓“发布订阅”只是观察者模式的松耦合变体:把 Subject 拆成 Publisher 和 EventBus,让 observer 订阅 topic 字符串或类型 ID,而非绑定具体对象。
核心区别只在注册方式和通知路由逻辑,底层数据结构和生命周期管理完全一致。强行分两套,只会让 EventBus::publish() 和 Subject::notify() 重复实现同一套线程安全、弱引用、延迟处理逻辑。
- 用
std::type_index当 topic:注册时传typeid(EventType),发布时查表 —— 零运行时字符串开销,类型安全 - 如果真需要字符串 topic(比如插件系统动态加载),用
std::unordered_map<:string std::vector>>></:string>,但记得加读写锁,别用std::map拖慢高频发布 - 别为了“看起来像发布订阅”就引入消息队列、序列化、跨进程通信 —— 那已经超出 C++ 进程内对象协作的范畴了
最难的从来不是“怎么写 notify”,而是“谁来决定 observer 的生命周期结束时机”。哪怕用了 weak_ptr,也得有人调 reset();哪怕用了 lambda,也得确保捕获的变量没提前析构。这些边界,没法靠模式名字自动兜住。



















