用std::function+观察者模式可解耦事件源与处理者:事件源只发通知,观察者只注册回调;需避免裸指针、支持取消订阅,并用weak_ptr或shared_ptr管理生命周期。

用 std::function + 观察者模式解耦事件源与处理者
事件驱动程序的核心不是“谁触发”,而是“谁响应不依赖谁触发”。C++ 没有原生事件系统,但用 std::function 组合观察者模式就能干净实现。关键在于:事件源(如按钮、socket)只管发通知,不关心谁在听;观察者(如UI控制器、业务逻辑)只注册回调,不感知事件从哪来。
常见错误是把事件处理逻辑硬编码进事件源类里,导致修改一个功能就得改多个类。更糟的是直接传 this 指针做回调,引发悬挂指针或生命周期错配。
- 事件源类应只持有一组
std::function<void event></void>,不持有任何具体观察者对象的引用 - 注册时推荐用 lambda 捕获局部变量,避免裸指针;若需捕获 this,务必配合 weak_ptr 或手动管理生命周期
- 取消订阅必须支持——否则对象析构后回调仍被调用,
std::vector存储 handler 时,删除操作要 O(n),可考虑用std::unordered_map配唯一 ID 管理
Qt 的信号槽机制如何避免跨线程崩溃
Qt 的 QObject::connect() 默认使用 AutoConnection,看似省事,但多线程下极易出错:发送线程调用 emit signal() 时,若接收对象在另一线程,而你没显式指定连接类型,Qt 可能误判为同线程并执行 DirectConnection,结果在错误线程访问 UI 对象,直接 crash。
真实场景中,网络线程收到数据后 emit 信号,希望主线程更新界面——这必须走 QueuedConnection,靠事件循环投递。
- 跨线程连接务必显式写
Qt::QueuedConnection,别依赖自动推导 - 信号参数必须是
QMetaType注册过的类型,自定义结构体要调用qRegisterMetaType<mystruct>()</mystruct>,否则队列化失败静默丢弃 - 若需等待槽执行完成再继续(如配置同步),才用
Qt::BlockingQueuedConnection,但会阻塞发送线程,慎用
Boost.Asio 中事件循环与 handler 生命周期怎么对齐
Asio 的异步操作(如 async_read、async_wait)本质是把 handler 注入 I/O 服务线程的队列。handler 的生命周期必须覆盖整个异步操作周期——哪怕操作立刻完成(如缓冲区已有数据),handler 也会被立即调用;若 handler 在操作完成前销毁,就会调用已释放内存,UB(未定义行为)。
最典型踩坑点:用栈上 lambda 做 handler,且没延长其生存期。Asio 不拷贝 handler,只存储引用或移动后的可调用体。
- 永远用
shared_from_this()包裹成员函数作为 handler,前提是类继承std::enable_shared_from_this - 避免在 lambda 中捕获局部变量指针或引用;若必须,确保该作用域生命周期 > 异步操作预期耗时
- 调用
io_context::stop()后,未完成的异步操作会被取消,但已排队的 handler 仍会执行——所以停止前建议主动 cancel 所有 pending 操作
自定义事件循环里,如何防止 handler 递归重入导致栈溢出
手写事件循环(比如用 std::queue<:function>></:function> + while(!q.empty()) { q.front()(); q.pop(); })看似简单,但一旦某个 handler 再次 post 新任务到同一队列,就可能无限嵌套调用,最终栈溢出。这不是理论风险——GUI 事件、定时器回调、甚至日志输出都可能间接触发新事件。
Qt 和 Asio 都内置了防重入保护,但自己写的轻量循环常忽略这点。
- 加一个运行中标志位
bool is_running = false,每次循环开始置 true,结束置 false;post 时若is_running为 true,则 push 到队列;否则直接执行 - 更健壮的做法是分两层队列:当前帧执行队列(正在 run 的) + 下一帧待执行队列(post 进来的),每轮只清空当前帧
- 不要在 handler 里直接调用
run_once()或类似入口函数,这等于手动开启递归,绕过所有防护
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











