使用std::function+vector可实现轻量级观察者模式,支持任意可调用对象注册与通知,通过拷贝容器避免迭代器失效,并可扩展为带参回调如DataCallback,注意lambda捕获的生命周期安全。

用 std::function + vector 实现轻量级观察者注册与通知
不需要引入第三方库或复杂模板,C++11 起就能用 std::function 统一回调类型,配合 std::vector 存储观察者。关键不是“多态接口”,而是“松耦合的函数注册”。
- 观察者不继承基类,任意可调用对象都行:普通函数、lambda、绑定后的成员函数
- 被观察者只持有一个
std::vector<:function>> m_observers</:function> - 注册用
add_observer(),通知用notify(),内部遍历调用 - 注意:若回调中可能移除自身(比如注销逻辑),需避免迭代器失效——建议先拷贝容器或使用索引遍历
class Subject {
private:
std::vector<std::function<void()>> m_observers;
public:
void add_observer(std::function<void()> obs) {
m_observers.push_back(obs);
}
void notify() {
// 避免边遍历边修改导致崩溃
auto observers = m_observers;
for (const auto& obs : observers) {
obs();
}
}
};
处理带参数的事件回调(如 on_data_received(int, const std::string&))
硬编码 void() 无法传递上下文信息,实际业务中几乎总是需要参数。直接把 std::function 的签名改成具体事件类型即可,不破坏原有结构。
- 定义事件类型:比如
using DataCallback = std::function<void const std::string>;</void> - 存储容器改为
std::vector<datacallback></datacallback> -
notify()变成notify(int code, const std::string& msg),转发参数给每个观察者 - lambda 捕获局部变量时注意生命周期:若 subject 生命周期长于 lambda 捕获的栈变量,会引发悬垂引用
class NetworkSubject {
public:
using Callback = std::function<void(int code, const std::string& msg)>;
private:
std::vector<Callback> m_callbacks;
public:
void add_callback(Callback cb) { m_callbacks.push_back(cb); }
void notify(int code, const std::string& msg) {
for (const auto& cb : m_callbacks) {
cb(code, msg);
}
}
};
// 使用示例
NetworkSubject net;
int local_id = 42;
net.add_callback([local_id](int c, const std::string& m) {
std::cout << "ID:" << local_id << " got: " << m << "\n";
});
解决多线程下观察者增删与通知的竞态问题
一旦涉及异步回调(比如网络线程触发 notify,UI 线程注册/注销),裸 vector + 拷贝遍历就不安全了。必须加锁,但锁粒度影响性能和死锁风险。
- 推荐用
std::shared_mutex(C++17):读多写少场景下,多个线程可同时读(通知),仅注册/注销时独占写 - 避免在回调里调用
add_callback()或remove_callback()—— 容易造成锁重入或递归通知 - 更稳妥的做法是把“待删除项”暂存到局部容器,在 notify 结束后统一清理,减少临界区时间
- 不要用
std::mutex包裹整个 notify 循环:会阻塞其他线程注册,且拉长锁持有时间
为什么不用虚函数接口 + std::shared_ptr<IObserver>?
传统教科书式观察者模式常定义抽象 IObserver 接口,让观察者继承并实现 update()。这在 C++ 中反而增加负担:
立即学习“C++免费学习笔记(深入)”;
- 每个观察者都要写一个类,哪怕只响应一次事件
- 必须用
std::shared_ptr管理生命周期,否则容易 dangling pointer - 虚函数调用有间接跳转开销,对高频事件(如帧更新)不友好
- 无法直接捕获局部变量,得靠额外成员字段传参,灵活性差
- 现代 C++ 更倾向“组合优于继承”,
std::function天然支持值语义和移动
真正难的不是注册和调用,是生命周期管理——谁负责释放回调?什么时候释放?别让 subject 持有 long-lived lambda 捕获了 this 指针却忘了对象已析构。










