裸指针用于观察者模式是为了避免循环引用和智能指针开销,前提是观察者必须在被观察对象销毁前显式注销;安全做法包括析构时置active标志、通知前检查isactive、用副本遍历防止迭代器失效。

为什么不用智能指针而用裸指针实现观察者模式?
裸指针在观察者模式中常用于避免循环引用或简化生命周期管理——比如被观察对象生命周期远长于观察者,且观察者可能随时注销。这时用 std::weak_ptr 反而增加开销和复杂度;而 std::shared_ptr 容易导致被观察对象无法析构(观察者还持有着它)。裸指针只负责“观察”,不参与所有权管理,前提是使用者明确知道:观察者必须在被观察对象销毁前主动注销。
如何安全地注册/注销裸指针观察者?
关键不是“能不能用裸指针”,而是“怎么防野指针”。常见错误是观察者析构后,被观察对象仍调用其 update() 方法,触发崩溃。
- 注册时只存
Observer*,不增引用计数 - 注销必须显式调用(例如在观察者析构函数里调用
subject->removeObserver(this)) - 通知前必须检查指针有效性:不是 nullptr,且未被释放(仅靠裸指针无法自动判断,所以需配合手动标记或外部管理)
更稳妥的做法是引入轻量级有效性标记:
class Observer {
public:
Observer() { active_ = true; }
virtual ~Observer() { active_ = false; }
virtual void update() = 0;
bool isActive() const { return active_; }
private:
mutable bool active_;
};
被观察者在 notify() 中跳过 !obs->isActive() 的项——这比空指针检查更可靠,但依赖观察者正确调用基类析构。
std::vector 还是 std::list?
选 std::vector 还是 std::list 主要看操作频次和顺序要求:
- 注册/注销频繁且无序 →
std::list删除 O(1),但遍历缓存不友好 - 通知密集、注册注销少 →
std::vector遍历快,删除可用 erase-remove 惯用法:observers_.erase(std::remove(observers_.begin(), observers_.end(), obs), observers_.end()) - 若观察者数量极少(std::vector 降低认知负担
注意:不能用 std::unordered_set<observer></observer> ——裸指针哈希值不稳定,且无法保证析构顺序,容易误删。
为什么 notify() 中不能边遍历边删除?
这是裸指针观察者最常踩的坑:在 for (auto* obs : observers_) 循环里调用 obs->update(),而某个 update() 内部又触发了 removeObserver(this),导致 observers_ 容器迭代器失效,后续访问越界。
- 解决方法一:先收集待删指针,循环结束后批量清理
- 解决方法二:遍历前拷贝一份指针副本(
auto copies = observers_),再对副本调用update() - 解决方法三:改用反向遍历 +
erase()(仅适用于std::vector,且需手动维护索引)
推荐方案二——简单、安全、代码清晰。哪怕观察者有 100 个,拷贝几十字节也远快于同步锁或智能指针开销。
裸指针实现观察者模式本身不难,难的是所有人对“谁负责在哪一刻注销”达成一致。一旦有人忘了调用 removeObserver,或者注销时机错位(比如在 update() 里删自己,但容器正在遍历),就只剩 core dump 等着你。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











