C++中如何实现简单的观察者模式?(利用虚函数接口或函数指针)

14次阅读

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

C++中如何实现简单的观察者模式?(利用虚函数接口或函数指针)

用纯虚函数定义 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)替代手写,它们内置了连接管理和自动断连

text=ZqhQzanResources