observer接口需抽象化以解耦生命周期,用std::shared_ptr管理观察者并复制列表通知,避免迭代器失效;std::function回调须确保捕获对象生命周期安全。

Observer 接口设计必须支持多态和生命周期解耦
观察者模式的核心不是“谁通知谁”,而是“被观察者不依赖具体观察者类型”。C++ 中最容易踩的坑是直接存储 std::vector<concreteobserver></concreteobserver>,导致编译期强耦合、无法动态添加不同类型的观察者。
正确做法是定义抽象接口:
class Observer {
public:
virtual ~Observer() = default;
virtual void onNotify(const std::string& event) = 0;
};
所有具体观察者继承它;被观察者(Subject)只持有 std::vector<:unique_ptr>></:unique_ptr> 或 std::vector<observer></observer>(若观察者由外部管理生命周期)。注意:用 std::weak_ptr 替代裸指针可避免悬挂指针,但需配合 std::shared_ptr 管理观察者生命周期。
Subject 的 attach/detach 必须处理重复注册与空悬指针
实际运行中,同一个观察者可能被多次 attach(),或在通知中途被销毁。裸指针方案下,detach() 若只用 std::find + erase,会漏掉重复项;若不用 erase-remove 惯用法,还会触发未定义行为。
立即学习“C++免费学习笔记(深入)”;
推荐写法:
void Subject::attach(std::shared_ptr<Observer> obs) {
if (obs && std::find(observers_.begin(), observers_.end(), obs) == observers_.end()) {
observers_.push_back(obs);
}
}
void Subject::detach(std::shared_ptr<Observer> obs) {
observers_.erase(
std::remove(observers_.begin(), observers_.end(), obs),
observers_.end()
);
}
- 用
std::shared_ptr自动管理观察者生存期,detach()前无需检查空悬 - 重复
attach()被显式拒绝,避免冗余通知 - 不建议用
std::weak_ptr存储——通知时需lock(),失败则跳过,逻辑分散且易漏处理
notify() 调用期间禁止修改观察者容器
这是最隐蔽的崩溃来源:遍历 observers_ 时,某个 Observer::onNotify() 内部又调用 subject->detach(this),导致迭代器失效。
安全做法有二:
- 复制一份观察者列表再遍历:
auto observers_copy = observers_;,然后对副本调用onNotify()—— 开销小,最常用 - 改用回调标记机制:先收集要移除的观察者 ID,在 notify 完后再批量清理 —— 适合高频通知场景
- 绝对不要在
for (auto& o : observers_)循环体内调用detach()或clear()
std::function + lambda 可替代 Observer 接口,但要注意捕获生命周期
对于简单场景,用 std::function<void std::string></void> 直接注册回调更轻量,省去继承接口的样板代码。
但常见错误是:
// ❌ 危险:捕获局部变量或 this,析构后调用 crash
void foo() {
Subject s;
int local = 42;
s.attach([&local](const auto& e) { std::cout << local; }); // local 已销毁
}
安全写法:
- 只捕获值:
[val = some_int](...){...} - 捕获
shared_from_this()(类需继承std::enable_shared_from_this) - 注册时传入
std::shared_ptr并在 lambda 中持有它,防止被提前析构
这种方案本质是“函数式观察者”,不适用于需要统一管理、查询、批量控制观察者状态的复杂系统。











