用std::function+std::vector实现观察者模式需解决迭代器失效、线程安全与生命周期管理:推荐std::list删除、std::shared_ptr管理观察者、std::any支持多类型参数,并采用“快发慢处”异步模型保障实时性。

用 std::function + std::vector 实现轻量级事件订阅
观察者模式在 C++ 里不靠语言内置机制,得自己搭骨架。最常用也最灵活的方式是用 std::function 存回调,std::vector 存多个观察者。这种方式不依赖第三方库,C++11 起就能跑,适合大多数内部事件通知场景(比如 UI 状态变更、配置重载完成)。
关键点在于:订阅时把 lambda 或函数对象塞进容器;发布时遍历调用——但要注意迭代器失效和线程安全问题。
- 避免在回调里调用
unsubscribe(),否则可能触发vector::erase导致后续迭代器失效 - 如果回调执行时间长,考虑用异步队列解耦,别卡死发布线程
- 用
std::shared_ptr包裹观察者可解决生命周期管理问题,但会引入引用计数开销
class EventManager {
std::vector> listeners_;
public:
void subscribe(std::function cb) {
listeners_.push_back(cb);
}
void publish(int value) {
for (const auto& cb : listeners_) cb(value); // 注意:不处理异常传播
}
}; 如何安全地在回调中移除自身订阅
这是实际编码中最容易翻车的地方:一边遍历 vector,一边删元素,iterator 失效后继续 ++ 就是未定义行为。标准解法是两遍扫描——先标记要删的索引,再倒序删除;或者改用 std::list 配合 erase 返回下一个有效迭代器。
- 推荐用
std::list<:function>>:删除节点不影响其他迭代器 - 若坚持用
vector,可用 erase-remove 惯用法:listeners_.erase(std::remove_if(...), listeners_.end()) - 更稳妥的做法是让订阅返回一个
std::unique_ptr句柄,由句柄控制是否激活,而非直接从容器删
用 std::any 支持多类型事件参数
硬写成 void(int) 太死板。用 std::any(C++17)能传任意类型参数,配合 std::type_index 做事件分类,比 void* + 枚举更类型安全。
立即学习“C++免费学习笔记(深入)”;
- 发布时:
publish(std::type_index(typeid(MyEvent)), std::any(MyEvent{...})) - 订阅时需显式
std::any_cast,失败会抛(data) std::bad_any_cast - 注意
std::any有拷贝开销,大对象建议传std::shared_ptr
线程安全不能只加个 mutex 就完事
加锁保护容器读写只是基础。真正麻烦的是:发布期间某个回调耗时很长,或引发死锁(比如回调里又去获取同一把锁),或导致发布函数阻塞太久影响实时性。
- 简单场景可用
std::shared_mutex:读多写少时提升并发度 - 高吞吐场景建议“快发慢处”:发布函数只往无锁队列(如
moodycamel::ConcurrentQueue)投递事件,另起线程消费 - 千万别在回调里调用
publish()形成嵌套发布,极易栈溢出或逻辑混乱
观察者模式真正的复杂点不在结构,而在生命周期绑定和调用上下文——谁负责释放回调?回调里能不能 throw?事件要不要排队?这些问题没想清楚,代码上线后大概率变成偶发 crash 的根源。










