用std::function+std::vector可实现轻量安全事件系统:统一回调类型、通知前拷贝容器防迭代器失效、支持带参签名、用weak_ptr破循环引用、避免裸指针和悬垂捕获。

用 std::function + std::vector 实现最简事件系统
能跑通、不依赖第三方、C++11 起就可用——这是多数中小型项目真正需要的观察者机制。核心不是“多态接口”,而是把回调抽象成统一可调用对象,让被观察者只管通知,不管观察者是谁。
-
std::function封装一切:普通函数、lambda、std::bind绑定的成员函数,只要签名匹配就能注册 - 存储用
std::vector<:function>></:function>(或带参版本),避免模板爆炸和基类继承耦合 - 通知时先拷贝容器:
auto observers = m_observers;,再遍历调用,彻底规避“边通知边增删导致迭代器失效”问题 - 别写裸指针回调:比如
void(*)()或捕获局部变量的 lambda,一旦被观察者生命周期长于捕获对象,就是悬垂引用
带参数的通知怎么写?别硬套 void()
实际业务里几乎从不用无参通知。温度变化要传 float,网络响应要传 int 和 std::string,UI 更新要传 const Widget& ——直接改 std::function 签名就行,结构完全不变。
- 定义类型别名更清晰:
using DataCallback = std::function<void const std::string></void> - 存储容器改为
std::vector<datacallback></datacallback>,notify()改成notify(int code, const std::string& msg) - lambda 捕获
this时务必检查生命周期:如果被观察者是单例或全局对象,而观察者是栈上对象,[this]() { ... }会 crash - 推荐用
shared_from_this()+weak_ptr包装回调,例如:[self = weak_from_this()](int c, const std::string& m) { if (auto p = self.lock()) p->onResponse(c, m); }
为什么不能在通知中增删观察者?
这不是风格建议,是 C++ 容器行为决定的硬限制。遍历 std::vector 时调用 erase() 或 push_back(),轻则跳过元素、重复调用,重则触发未定义行为(UB)——尤其在 Release 模式下可能静默出错,调试极难定位。
- 典型错误现象:
detach()在某个update()回调里被调用,结果下一个观察者收不到通知,或程序崩溃 - 安全做法只有两种:一是如前所述,通知前拷贝整个容器;二是引入标识位(如
bool m_to_remove),通知结束后统一清理 - Qt 的
QObject::disconnect()可以在槽函数里安全调用,是因为它内部做了延迟移除(queued connection),纯 C++ 没这层机制
循环引用怎么破?std::weak_ptr 不是摆设
当观察者是某个对象的成员函数,且用 std::bind 或 lambda 捕获 this 注册到被观察者时,极易形成 Subject → Observer → Subject 强引用闭环,导致内存泄漏。
立即学习“C++免费学习笔记(深入)”;
- 常见错误写法:
subject->attach(std::bind(&MyClass::onEvent, this, _1));——this被强持有 - 正确解法一:被观察者侧存
std::vector<:weak_ptr>></:weak_ptr>,每次通知前lock()判断是否还存活 - 正确解法二:注册方主动管理,用
shared_from_this()构造临时shared_ptr,再转成 weak 捕获,确保被观察者不延长其生命周期 - 注意:
std::signal/std::slot不是标准库组件,C++ 标准至今没有内置信号槽,别被名字误导去查不存在的文档










