核心是用std::vector存储回调,以值语义避免悬挂;推荐weak_ptr自动管理生命周期,兼顾安全与简洁。

用 std::function + std::vector 实现轻量观察者注册表
核心是让被观察者持有一组可调用对象,不依赖虚函数或接口类,避免强制继承。每个观察者只需提供一个匹配签名的回调(比如 void(const Event&)),注册时用 std::function 包装,存进 std::vector。
常见错误是直接存裸指针或引用,导致观察者析构后被调用——必须用值语义存储;也有人误用 std::shared_ptr 管理观察者生命周期,反而引入循环引用风险。
实操建议:
- 被观察者类中声明
std::vector<:function t>> m_observers,其中T是事件类型 - 提供
subscribe()接口,接受任意可调用对象(lambda、函数指针、绑定对象),内部调用emplace_back() - 触发通知时用范围 for 遍历调用,不检查有效性(由使用者保证生命周期)
- 如需支持取消订阅,改用
std::vector<:pair std::function>>>,配合erase(remove_if(...))
避免 std::function 拷贝开销与悬挂引用
std::function 构造和调用都有隐式开销,尤其捕获大对象的 lambda 会被完整拷贝。更严重的是:若 lambda 捕获了局部变量或栈对象的引用,注册后该作用域退出,后续通知就会访问悬挂内存。
立即学习“C++免费学习笔记(深入)”;
典型错误写法:obs.subscribe([&x]{ do_something(x); }); —— x 是函数内局部变量,注册完就销毁。
正确做法:
- 只捕获值(
[x]{...})或智能指针([ptr = shared_from_this()]{...}) - 对短生命周期对象,改用弱引用模式:观察者主动在析构时调用
unsubscribe() - 若性能敏感,可用函数指针 +
void*用户数据替代std::function,但丧失类型安全
用 std::weak_ptr 自动管理观察者生命周期(推荐场景)
当观察者是堆分配且有明确 owner(比如 std::shared_ptr),可用 std::weak_ptr 存储其引用,每次通知前先 lock()。这样既避免悬挂,又不用手动 unsubscribe。
关键点在于:被观察者不参与所有权,只持有弱引用;观察者销毁时自动失效,无需干预。
示例结构:
class Subject {
std::vector> m_observers;
public:
void notify(const Event& e) {
for (auto it = m_observers.begin(); it != m_observers.end(); ) {
if (auto obs = it->lock()) {
obs->onEvent(e);
++it;
} else {
it = m_observers.erase(it); // 自动清理已销毁的
}
}
}
};
为什么不用信号槽(如 Qt)或第三方库?
Qt 的 QObject 机制要求所有类继承自 QObject,且依赖 moc 编译器,不适合纯 C++17/20 项目或嵌入式环境。Boost.Signals2 功能强但二进制体积大、编译慢。
自己封装的价值在于:
- 完全控制内存模型(栈/堆/无分配)
- 可按需裁剪:比如去掉线程安全、去掉类型擦除、固定最大观察者数
- 调试友好:断点直接停在通知循环里,不陷进模板展开
- 与 RAII 自然结合:例如在
ScopeGuard中自动注册/注销
真正麻烦的不是实现,而是决定谁负责生命周期、是否需要线程安全、事件是否可排队——这些得根据具体模块边界来定,不能靠“通用封装”一劳永逸。










