C++轻量级观察者模式通过RAII封装ObserverHandle实现自动注册/注销:构造时注册回调并保存weak_ptr,析构时查找并移除对应shared_ptr;Subject用vector存shared_ptr,notify时lock调用,适用于≤100观察者场景。

在C++中实现轻量级观察者模式时,需确保观察者注册后能自动注销回调,避免悬空指针或重复析构——尤其当被观察对象生命周期短于观察者时,手动注销极易遗漏。
设计核心:RAII封装观察者句柄
定义一个 RAII 风格的 ObserverHandle 类,其构造时注册回调,析构时自动反注册。
第一步:声明被观察者基类接口,含 register_observer 与 unregister_observer 成员函数,返回类型为 ObserverHandle。
第二步:在被观察者内部维护一个 std::vector<std::function<void()>> 或更安全的 std::vector<std::weak_ptr<void>>(若用 shared_ptr 管理观察者);但此处采用更轻量方案——用 std::list<std::function<void()>>::iterator 存储每个观察者的注销句柄。
立即学习“C++免费学习笔记(深入)”;
第三步:ObserverHandle 构造函数接收被观察者引用和一个 std::function<void()> 回调,并立即调用被观察者的注册函数,获取对应注销迭代器;该迭代器被保存为私有成员;析构时,若迭代器有效,则调用被观察者的注销函数——【必须检查迭代器是否已失效,否则 erase 已 erase 过的迭代器将导致未定义行为】。
关键实现:注销逻辑的双重保障
方法一:使用 std::list::erase 后立即将迭代器置为 list.end(),并在 ObserverHandle 析构前判断是否等于 end()。
方法二:改用 std::vector 存储回调,但不直接存迭代器,而改用索引 + 布尔标志位数组;每次注销仅标记为失效,实际清理延迟到下一次通知前批量执行——这避免了 erase 迭代器失效问题,但增加通知开销。
方法三(推荐):在被观察者中维护 std::vector<std::shared_ptr<std::function<void()>>>,每个 ObserverHandle 持有一个 std::weak_ptr<std::function<void()>>;析构时尝试 lock 并从 vector 中移除对应项;【vector 删除需线性查找,适合观察者数量 ≤100 的场景】。
完整可运行源码片段
定义被观察者类 Subject,含 notify() 和 register_observer();后者返回 ObserverHandle:
class Subject {
private:
std::vector<std::shared_ptr<std::function<void()>>> observers_;
public:
auto register_observer(std::function<void()> cb) -> ObserverHandle {
auto sp = std::make_shared<std::function<void()>>(std::move(cb));
observers_.push_back(sp);
return ObserverHandle(*this, sp);
}
void notify() { for (auto& obs : observers_) if (auto p = obs.lock()) (*p)(); }
};
class ObserverHandle {
Subject& subj_;
std::weak_ptr<std::function<void()>> wp_;
public:
ObserverHandle(Subject& s, std::shared_ptr<std::function<void()>> sp) : subj_(s), wp_(sp) {}
~ObserverHandle() {
auto it = std::find_if(subj_.observers_.begin(), subj_.observers_.end(),
[this](const auto& ptr) { return ptr.get() == wp_.lock().get(); });
if (it != subj_.observers_.end()) subj_.observers_.erase(it);
}
};


















