用std::function+std::vector可实现轻量观察者模式:注册回调(lambda/函数/绑定成员函数),通知时遍历调用,无虚函数开销;需防悬挂引用(推荐detach()或shared_ptr管理生命周期)。

用 std::function + std::vector 实现轻量观察者注册与通知
不需要引入 Boost 或 Qt,C++11 起就能用 std::function 和 std::vector 搭出可用的观察者模式。核心是把回调抽象为可调用对象,避免继承和虚函数开销。
关键点在于:观察者不继承基类,被观察者不持有具体类型,只存 std::function<void></void> 或带参数的变体(如 std::function<void></void>)。
- 注册时用
std::function包装 lambda、普通函数或成员函数(需绑定this) - 通知时遍历 vector,直接调用
func()—— 无虚表跳转,性能接近裸函数指针 - 注意:若注册的是类成员函数,必须用
std::bind或 lambda 捕获this,否则编译失败
#include <vector>
#include <functional>
class Subject {
private:
std::vector<std::function<void(int)>> observers;
public:
void attach(std::function<void(int)> obs) {
observers.push_back(obs);
}
void notify(int value) {
for (auto& obs : observers) {
obs(value); // 直接调用,无多态开销
}
}
};
// 使用示例
int main() {
Subject sub;
int count = 0;
// lambda 观察者
sub.attach([&count](int v) { count += v; });
// 普通函数观察者
auto log = [](int v) { printf("log: %d\n", v); };
sub.attach(log);
sub.notify(42); // 触发两个回调
}
处理成员函数回调时如何避免悬挂引用
最容易踩的坑:用 [this] 捕获成员函数到 std::function,但被观察者生命周期长于观察者对象 —— this 变成悬垂指针,调用时崩溃。
这不是语法问题,而是所有权语义缺失导致的运行时错误。标准库不检查 std::function 内部是否还有效。
立即学习“C++免费学习笔记(深入)”;
- 方案一:用
std::shared_ptr管理观察者对象,lambda 中捕获shared_from_this() - 方案二:提供
detach()接口,由观察者在析构前显式注销 - 方案三:改用信号槽库(如 libsigc++),自带连接生命周期管理
纯标准库方案中,detach() 最轻量,但依赖人工配对调用,容易遗漏。
std::function 的性能代价与替代选项
std::function 有小对象优化(SBO),但一旦捕获大对象或使用堆分配(如绑定多个参数的 std::bind),就会触发内存分配 —— 在高频通知场景下可能成为瓶颈。
- 若观察者数量固定且极少(≤3),可考虑模板参数化:用
std::tuple存不同类型的回调,编译期展开 - 若只允许函数指针(无状态),直接用
void(*)()类型,零开销 - Qt 的
QObject::connect走的是元对象系统,支持跨线程队列,但重量级;这里讨论的是无框架的轻量实现
多数业务逻辑通知频率不高,std::function 的开销可忽略;真卡在这里,说明设计上可能已混淆了“事件”和“热路径计算”。
为什么不用虚函数实现经典观察者模式
教科书常写 Observer 抽象基类 + update() 虚函数,但 C++ 中这会强制继承、引入虚表、破坏内联机会,且每个观察者都要写一个类。
- 虚函数方案适合需要运行时动态增删多种异构行为的场景(如 GUI 控件系统)
- 但多数业务中,观察者只是“收到数据后做点事”,用
std::function更直白、更易测试、更少样板代码 - 虚函数还带来对象切片风险:若把派生类对象传给接受基类引用的
attach(),会丢失派生部分
现代 C++ 倾向用组合代替继承,用类型擦除(std::function)代替运行时多态 —— 不是回避设计模式,而是选更贴合语言特性的表达方式。











