Observer接口必须声明虚析构函数,否则多态删除会导致析构不完整、内存泄漏或未定义行为;纯接口也需显式声明,编译器不会自动生成。

Observer 接口必须用虚析构函数
不加 virtual ~Observer() = default; 会导致派生类对象通过基类指针删除时析构不完整,内存泄漏或未定义行为。这是 C++ 观察者模式最常被忽略的底层陷阱。
实际场景中,Subject 通常持有 std::vector<:unique_ptr>> 或 std::vector,而后者更常见(避免强制所有权转移)。无论哪种,只要涉及多态删除,就必须有虚析构。
- 用
std::unique_ptr→ 必须虚析构 - 用裸指针
Observer*→ 仍需虚析构(因为delete ptr会调用基类析构) - 若 Observer 是纯接口(无数据成员),也仍需显式声明虚析构,否则编译器不会自动生成
std::function + std::vector 实现松耦合 Observer
比起继承接口,用回调函数注册更轻量、无侵入性,适合事件驱动或脚本化扩展场景。但要注意生命周期管理——std::function 捕获局部变量时极易悬垂。
典型错误是把 lambda 捕获了栈上对象后存进 Subject 的容器里,稍后调用就崩溃。安全做法是只捕获 this 或智能指针,或确保观察者对象比 Subject 活得久。
立即学习“C++免费学习笔记(深入)”;
class Subject {
std::vector> observers_;
public:
void attach(std::function cb) {
observers_.push_back(cb);
}
void notify(int value) {
for (auto& cb : observers_) cb(value);
}
};
// ✅ 安全:捕获 this(假设 ObserverImpl 生命周期可控)
struct ObserverImpl {
void onValue(int v) { / ... / }
void registerTo(Subject& s) {
s.attach([this](int v) { this->onValue(v); });
}
};
线程安全不是默认选项
std::vector::push_back 和遍历通知都不是原子操作。多线程下并发 attach() 和 notify() 会触发 data race,甚至导致迭代器失效或容器重分配时崩溃。
常见折中方案是读多写少时用 std::shared_mutex(C++17),或直接加 std::mutex 锁住整个通知流程。但锁粒度太大会降低吞吐,尤其通知链路长时。
- 避免在
notify()中调用可能阻塞或长时间运行的回调 - 若需异步通知,建议把回调转为 post 到线程池,而不是在锁内执行
- 不要在回调里调用
detach()—— 遍历时修改容器是未定义行为;应改用标记+延迟清理(如 erase-remove idiom)
std::weak_ptr 防止循环引用(当用 shared_ptr 管理 Observer)
如果 Subject 和 Observer 都用 std::shared_ptr 互相持有,就会形成循环引用,导致对象永远不析构。解决方案是 Subject 改用 std::weak_ptr 存储,每次通知前先 lock()。
这会带来额外开销(控制块访问 + 引用计数检查),但能彻底解决悬挂指针和泄漏问题。适用于 Observer 生命周期不可控(比如来自插件模块)的场景。
class Subject {
std::vector> observers_;
public:
void attach(std::shared_ptr obs) {
observers_.push_back(obs);
}
void notify() {
// 注意:这里要拷贝并过滤已销毁的 observer
std::vector> alive;
for (auto& w : observers_) {
if (auto s = w.lock()) alive.push_back(s);
}
for (auto& obs : alive) obs->update();
}
};
C++ 观察者最难的从来不是结构,而是谁负责生命周期、谁触发销毁、谁保证调用时对象还活着——这些不能靠模式图解决,得靠具体容器语义和所有权约定。











