用纯虚函数定义Observer接口最清晰,即声明virtual void update(const std::string& event) = 0,强制子类实现,避免对象切片和运行时类型擦除,Subject用std::vector管理生命周期。

用纯虚函数定义 Observer 接口最清晰
虚函数接口是 C++ 观察者模式最常用、最符合面向对象语义的方式。关键在于把 update 声明为纯虚函数,强制子类实现,同时避免对象切片和运行时类型擦除问题。
常见错误是直接在基类里提供空的 update 实现(非纯虚),导致派生类忘记重写却无编译报错;或者用 std::function 替代接口,失去静态多态和零开销抽象的优势。
-
Observer基类只含一个纯虚函数virtual void update(const std::string& event) = 0; -
Subject持有std::vector<:unique_ptr>>,避免裸指针生命周期失控 - 注册时用
std::move转移所有权,禁止拷贝Observer对象(可删掉拷贝构造/赋值) - 通知时遍历容器调用
obs->update(event),多态分发由 vtable 完成,无额外开销
class Observer {
public:
virtual ~Observer() = default;
virtual void update(const std::string& event) = 0;
};
class Subject {
std::vector> observers;
public:
void attach(std::unique_ptr obs) {
observers.push_back(std::move(obs));
}
void notify(const std::string& event) {
for (const auto& obs : observers) {
obs->update(event);
}
}
};
用函数指针实现轻量级回调需注意调用约定
函数指针适合极简场景(比如嵌入式或性能敏感模块),但无法捕获对象状态,必须搭配上下文参数或静态成员函数使用。容易踩的坑是传入普通成员函数地址——它不是自由函数,不能直接转成 void(*)()。
若要绑定对象实例,必须用静态成员 + void* 上下文,或改用 std::function(但已偏离“函数指针”本意)。
立即学习“C++免费学习笔记(深入)”;
- 自由函数指针签名应为
void (*)(const char*, void*),第二个参数用于传递this - 注册时保存函数指针和
void*上下文,通知时一并传入 - 不能直接存
&MyClass::onUpdate—— 编译失败,成员函数指针与普通指针不兼容 - 相比虚函数,少了类型安全和自动内存管理,易出现悬空指针或类型误传
using Callback = void (*)(const char*, void*);class SubjectFP { struct Handler { Callback fn; void ctx; }; std::vector
handlers; public: void attach(Callback fn, void ctx) { handlers.push_back({fn, ctx}); } void notify(const char* event) { for (const auto& h : handlers) { h.fn(event, h.ctx); } } };// 使用示例: void log_update(const char e, void obj) { std::cout << "Log: " << e << "\n"; }
std::function + lambda 是折中方案,但有额外开销
如果需要捕获局部变量或快速原型验证,std::function 配合 lambda 最方便。但它底层可能触发堆分配(当闭包对象较大时),且每次调用有间接跳转开销,不适合高频通知路径。
另一个问题是生命周期管理:lambda 捕获局部变量后,若 Subject 生命周期长于该变量,就会变成悬空引用。必须确保捕获的是值([=])或显式延长依赖对象寿命。
- 声明容器为
std::vector<:function std::string>> - 注册时可直接写
[&obj]() { obj.handle(); },但注意obj必须比Subject活得久 - 比起虚函数,少了编译期多态,调试时栈帧更难追踪(内联受限)
- 如需零开销,应回退到虚函数接口;如需灵活性,接受这点开销也合理
观察者生命周期管理是核心难点
无论用哪种方式,最大的实际问题不是语法怎么写,而是谁负责销毁观察者、何时从 Subject 中移除。C++ 没有弱引用原语,std::weak_ptr 只适用于共享所有权场景,而多数观察者是独占或栈上对象。
虚函数方案中,Subject 持有 unique_ptr 表示强拥有,意味着观察者不能独立于 Subject 存活;函数指针方案则完全无所有权语义,全靠程序员手动保证有效性。这是所有 C++ 观察者实现中最容易出 bug 的地方。
- 不要让
Subject持有裸指针或引用 - 若观察者是栈对象,注册前必须确认其作用域覆盖整个通知周期
- 提供
detach()接口,允许观察者主动注销(需在Observer析构时调用) - 考虑用信号槽库(如
libsigc++或 Qt)替代手写,它们内置了连接管理和自动断连










