C++ 回调函数搞懂指南

📅 2026/7/1 2:04:58
C++ 回调函数搞懂指南
文章目录C 回调函数完全搞懂指南一句话说清楚一、从一个生活例子开始二、不用回调 vs 用回调2.1 不用回调死循环盯着2.2 用回调发完请求立刻返回结果到了自动处理三、回调的核心三要素四、我的项目中回调出现的每一处4.1 HTTP 路由——浏览器访问时触发4.2 IPC 请求处理器——收到消息时触发4.3 promise/future——异步等待结果4.4 串口任务完成 → 批次聚合4.5 并行查询多模块五、Qt 信号槽也是回调六、为什么不用死循环轮询七、回调的注意事项7.1 引用捕获的生命周期7.2 回调里不要做耗时操作八、常见应用场景汇总九、一句话记忆法C 回调函数完全搞懂指南一句话说清楚回调函数就是你写好的代码交给别人保管等事情发生时别人帮你调用。你失去了什么时候执行的掌控权但换来了不用在那干等的自由。一、从一个生活例子开始你去楼下快递柜寄快递正常做法同步自己盯着 你把快递放进柜子 → 站在柜子前面等快递员来取 → 快递员来了 → 你看着他取走 → 回家 问题你傻站了半小时什么都没干 回调做法异步留个电话 你把快递放进柜子 → 在系统里留个回调快递被取走时发短信通知我 → 立刻回家打游戏 → 半小时后手机响了您的快递已被取走 → 你知道完事了 好处你不用盯着该干嘛干嘛你留在系统里的那个发短信通知我就是回调函数。二、不用回调 vs 用回调2.1 不用回调死循环盯着// 查 ODU 设备温度double查温度(){while(true){发查询命令();sleep(10ms);if(设备回复了()){return设备回复的数据;}// 没回复继续循环等着}}// 调用doubletemp查温度();// 这行要卡 800ms期间什么都干不了处理温度(temp);致命问题线程被卡住这 800ms 内不能处理任何其他事情。2.2 用回调发完请求立刻返回结果到了自动处理// 查 ODU 设备温度异步void查温度(std::functionvoid(double)回调){发查询命令();把回调存起来;// 不执行只是记下来}// 这一行立刻返回不卡// 另一个线程收到设备回复时void收到回复(doublevalue){所有的回调(value);// 现在才执行}// 调用查温度([](doubletemp){处理温度(temp);// 这段代码 800ms 后才执行但调用者不用等});做其他事情();// 这行立刻执行不卡三、回调的核心三要素要素代码生活类比注册setCallback(函数)给快递公司留电话存储callback_ 函数快递公司记下你的号码触发callback_(参数)包裹到了快递员打你电话// 最小例子就三样东西std::functionvoid(int)callback;// ① 准备一个能装函数的变量callback[](intx){coutx;};// ② 把函数装进去此刻不执行callback(42);// ③ 调用它此刻才执行四、我的项目中回调出现的每一处4.1 HTTP 路由——浏览器访问时触发// webd/api_odu.cppserver.Get(/api/v1/odu/monitor,[ipc](req,res){// ↑ 这个 lambda 就是回调// 浏览器访问这个路径时httplib 框架才调用它writeJson(res,200,查询ODU数据());});4.2 IPC 请求处理器——收到消息时触发// odud/main.cppipc.setRequestHandler([manager](constIpcMessagereq){manager.handleRequest(req);});// 回调存进 request_handler_ 变量里// rxLoop 线程收到 request 消息时调用// 触发处ipc_client.hpp rxLoop()if(msg.typerequest){request_handler_(msg);// ← 调回调}4.3 promise/future——异步等待结果// ipc_client.hpp request()autofuturepromise.get_future();waiters_.emplace(seq,std::move(promise));// 回调 promise.set_value()send(msg);// 发请求returnfuture.get();// 阻塞等直到 rxLoop 调回调// 触发处rxLoop 收到 responseit-second.set_value(msg);// ← 调回调promise 的 set_value4.4 串口任务完成 → 批次聚合// odu_request_manager.cpp// 每个串口任务完成后调 finishBatchField// 最后一个任务调 finishBatchField 时会触发 ipc_.respond() —— 这个就是最终回调4.5 并行查询多模块// webd/api_odu.cppautoodustd::async(std::launch::async,[]{returnrequestData(ipc,odud,odu.get_monitor,{});});// 开线程线程里执行回调五、Qt 信号槽也是回调我在 Qt 里写的这些本质都是回调// Qt Cconnect(button,QPushButton::clicked,this,MyClass::onClicked);// onClikcked 就是回调存进 Qt 元对象系统鼠标点击时调用// QMLButton{onClicked:{console.log(点了)}// 这个 {} 就是回调Qt 替你存好了}Qt 信号槽C 原生回调存储Qt 元对象系统std::function变量注册connect()/onXxx: {}setHandler(lambda)触发emit signal()callback_(args)依赖必须 QObject moc纯 C 标准库零依赖一对多✅❌要自己实现跨线程✅❌要自己处理Qt 信号槽 加强版回调。嵌入式 Linux 没有 Qt只能用标准库的std::function。六、为什么不用死循环轮询// 轮询方式while(!消息到了){sleep(1ms);// CPU 空转浪费电}处理消息();// 回调方式收消息线程{recv();// 阻塞等待没消息就睡觉CPU 占用 0%回调(消息);// 有消息才被唤醒}轮询回调CPU 占用一直占着0%阻塞等待响应速度取决于轮询间隔来即响应代码耦合框架和业务粘死框架只管调回调不管内容七、回调的注意事项7.1 引用捕获的生命周期// ❌ 危险void设置回调(){Manager manager;ipc.setRequestHandler([manager](msg){// 按引用捕获manager.handleRequest(msg);});}// manager 析构了但回调还在 → 悬垂引用 → 崩溃// ✅ 安全本项目做法intmain(){Manager manager;// manager 在 main 栈上ipc.setRequestHandler([manager](msg){// 按引用捕获manager.handleRequest(msg);});while(true)sleep(24h);// main 不退出manager 一直活着}规则引用捕获时被捕获对象必须比回调活得久。7.2 回调里不要做耗时操作// ❌ 危险ipc.setRequestHandler([](msg){发串口请求并等800ms();// 卡住 rxLoop 线程其他消息收不到了});// ✅ 正确本项目做法ipc.setRequestHandler([](msg){把任务扔进队列();// 几微秒立刻返回// 耗时操作在别的线程里做});八、常见应用场景汇总场景例子网络/串口 I/O发请求 → 等回复 → 收到时调回调UI 事件点按钮 → 调回调定时器设一个定时器“5 秒后调这个回调”异步计算提交任务“算完了调这个回调”库/框架扩展框架写好流程用户填回调std::sort的比较函数中断处理硬件中断 → 调 ISR 回调多结果聚合每个结果到了调回调足够数量时触发最终处理九、一句话记忆法正常调用 你去敲门。回调 你留电话号码别人打给你。正常你知道什么时候调你掌控时机回调你写好逻辑交给别人别人掌控时机回调不是为了炫技是为了不卡线程异步解耦框架和业务一份框架代码多处复用省 CPU阻塞等待代替轮询