不能直接用 std::function 存储所有回调,因其会丢失签名信息、无法编译期校验且不支持带参通知;应采用模板化 observer 基类,并通过类型擦除接口 inotifier 统一管理不同特化观察者。

为什么不能直接用 std::function 存储所有回调?
因为观察者要支持多种参数类型、返回值,且需避免运行时类型擦除开销。硬塞进 std::function<void></void> 会丢失签名信息,导致无法做编译期校验,也难以支持带参通知(比如 on_data_changed(int, const std::string&))。模板才是解耦接口与实现的正路。
如何定义泛型观察者基类并支持多事件类型?
核心是把事件类型作为模板参数,而不是把回调类型硬编码。观察者不继承自某个“通用Observer”,而是每个具体事件类型对应一个独立模板实例:
template<typename eventt>
class Observer {
public:
virtual ~Observer() = default;
virtual void on_notify(const EventT& e) = 0;
};</typename>
这样,Observer<dataupdateevent></dataupdateevent> 和 Observer<configreloadevent></configreloadevent> 完全无关,编译器能做强类型检查,也不会出现“传错事件类型”的运行时错误。
如何让被观察者支持添加/移除任意模板化观察者?
关键在于用类型擦除容器存不同特化版本的观察者,但擦除方式必须保留调用能力——用虚函数中转,而非 std::any 或 void*:
- 定义统一的内部接口
INotifier,含纯虚notify(const void* event) - 每个
Observer<t></t>派生类构造时绑定自己的on_notify,并在INotifier::notify中安全static_cast回去 - 被观察者只存
std::vector<:unique_ptr>></:unique_ptr>,添加时包装一层
示例片段:
<pre class="brush:php;toolbar:false;">template<typename eventt>
class ConcreteNotifier : public INotifier {
Observer<eventt>& obs_;
public:
explicit ConcreteNotifier(Observer<eventt>& obs) : obs_(obs) {}
void notify(const void* event) override {
obs_.on_notify(*static_cast<const eventt>(event));
}
};</const></eventt></eventt></typename>实际使用时最容易漏掉的两件事
一是观察者生命周期管理:模板本身不解决内存安全,若用 raw pointer 存 observer,容易悬空;建议配合 std::shared_ptr<observer>></observer>
二是事件对象的拷贝成本:如果 EventT 很大(比如含 std::vector<float></float>),每次 notify() 都传值可能成为瓶颈。此时应约定事件对象 immutable,并在 notify 接口中改用 const EventT&,同时确保被观察者持有的事件生命周期长于通知过程。
模板观察者模式不是“写一次就能套所有场景”的银弹,EventT 的设计粒度、通知时机、线程安全边界,都得按实际业务定——比如 UI 更新事件和后台任务完成事件,很可能需要完全不同的模板参数和生命周期策略。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











