C++ 回调函数(Callback Function)详解

回调函数 :将函数指针/可调用对象 作为参数传入另一个函数,在特定时机(如异步完成、事件触发、遍历结束)被被动调用 的函数,核心是解耦逻辑、实现灵活扩展。

一、核心分类(C++ 3种主流实现)

实现方式 适用场景 优点 缺点
函数指针 纯C兼容、简单回调 性能最高、无依赖 不支持类成员、无状态
函数对象(仿函数) 带状态的回调 可存储数据、灵活 代码稍繁琐
std::function + Lambda 现代C++、复杂场景 支持Lambda/成员函数、易用 有轻微性能开销

二、代码示例(从基础到现代C++)

1. 基础版:函数指针(C风格,最底层)

原理:用 函数指针 接收回调地址,在目标函数中调用。

cpp 复制代码
  
#include <iostream>
// 1. 定义回调函数类型(简化写法)
typedef void (*CallbackFunc)(int); 

// 2. 回调函数(被调用方)
void printResult(int val) {
    std::cout << "回调执行:结果 = " << val << std::endl;
}

// 3. 目标函数(调用方,接收回调指针)
void compute(int a, int b, CallbackFunc callback) {
    int res = a + b;
    callback(res); // 触发回调
}

int main() {
    compute(10, 20, printResult); // 传入回调函数名
    return 0;
}

输出:

plaintext

回调执行:结果 = 30

2. 进阶版:函数对象(仿函数,带状态)

原理:重载 operator() 的类对象,可存储成员变量(状态)。

cpp 复制代码
  
#include <iostream>

// 1. 仿函数类(带状态:前缀字符串)
class CallbackObj {
private:
    std::string prefix;
public:
    CallbackObj(const std::string& p) : prefix(p) {}
    // 重载()运算符,实现回调逻辑
    void operator()(int val) const {
        std::cout << prefix << ":计算结果 = " << val << std::endl;
    }
};

// 2. 模板目标函数(兼容任意可调用对象)
template <typename T>
void calculate(int x, int y, T callback) {
    callback(x * y); // 调用仿函数
}

int main() {
    CallbackObj obj("【仿函数回调】");
    calculate(5, 6, obj); // 传入对象
    return 0;
}

输出:

plaintext

【仿函数回调】:计算结果 = 30

3. 现代C++:std::function + Lambda(最常用)

原理 : std::function 封装任意可调用对象, Lambda 匿名函数简化写法,支持捕获外部变量

cpp 复制代码
  
#include <iostream>
#include <functional> // 必须包含

// 1. 用std::function定义回调类型(通用、灵活)
using Callback = std::function<void(int)>;

// 2. 目标函数
void processData(int data, Callback callback) {
    int result = data * 2;
    callback(result); // 触发回调
}

int main() {
    // Lambda回调:捕获外部变量(带状态)
    std::string tag = "Lambda回调";
    processData(15, [&](int res) {
        std::cout << tag << ":处理后数据 = " << res << std::endl;
    });
    return 0;
}

输出:

plaintext

Lambda回调:处理后数据 = 30

std::function 是类型擦除 (通用封装),而直接用模板是零成本抽象。Lambda 本身是一个匿名类对象,完全可以直接作为参数传递,不需要 std::function 这层包装。

方案一:使用函数模板(推荐,性能更好)

我们可以把 processData 改成模板函数,直接接收任意可调用对象(Lambda、函数指针、仿函数)。

cpp 复制代码
#include <iostream>
#include <string>

// 模板版本:直接接收 Lambda,无需定义 Callback 类型
template <typename Func>
void processData(int data, Func&& callback) {
    int result = data * 2;
    // 完美转发调用,保留右值属性
    std::forward<Func>(callback)(result); 
}

int main() {
    std::string tag = "Lambda回调";
    // 直接传 Lambda,无需 std::function
    processData(15, [&](int res) {
        std::cout << tag << ": 处理后数据 = " << res << std::endl;
    });
    return 0;
}

方案二:使用 auto 参数(C++20 简写模板)

如果编译器支持 C++20,写法更简洁:

cpp 复制代码
// C++20 简写模板
void processData(int data, auto&& callback) {
    int result = data * 2;
    callback(result);
}

4. 实战版:类成员函数作为回调

场景:回调逻辑封装在类中,需绑定 this 指针。

cpp 复制代码
  
#include <iostream>
#include <functional>

class Processor {
public:
    // 成员回调函数
    void onComplete(int code) const {
        std::cout << "成员函数回调:状态码 = " << code << std::endl;
    }
};

// 目标函数
void runTask(std::function<void(int)> callback) {
    callback(200); // 模拟任务完成
}

int main() {
    Processor p;
    // 绑定成员函数 + this指针
    runTask(std::bind(&Processor::onComplete, &p, std::placeholders::_1));
    return 0;
}

输出:

plaintext

成员函数回调:状态码 = 200

三、核心应用场景(必看)

  1. 异步编程:线程完成后回调通知(如 std::thread 配合回调)
  2. 事件驱动:按钮点击、网络请求完成(Qt信号槽本质是回调)
  3. 遍历/算法: std::sort 自定义比较、 std::for_each 遍历处理
  4. 框架设计:SDK暴露回调接口,用户自定义逻辑(如日志、错误处理)

四、关键注意事项

  1. 空指针检查:回调前判断指针是否为空,避免崩溃
  2. 生命周期:回调对象/函数需在调用时有效(避免野指针)
  3. 性能 :高频回调优先用函数指针/仿函数, std::function 有轻微开销
  4. 线程安全:多线程回调需加锁,避免数据竞争
相关推荐
lay_liu2 小时前
QoS质量配置
开发语言·智能路由器·php
sonnet-10292 小时前
拓扑排序的实现
java·c语言·开发语言·笔记·算法
hz_zhangrl2 小时前
CCF-GESP 等级考试 2026年3月认证C++二级真题解析
c++·gesp·c++二级·gesp2026年3月·gesp c++二级
SuperEugene2 小时前
Vue3 Pinia 状态管理规范:何时用 Pinia 何时用本地状态|状态管理与路由规范篇
开发语言·前端·javascript·vue.js·前端框架
学嵌入式的小杨同学2 小时前
STM32 进阶封神之路(二十四):低功耗实战全攻略 —— 电池供电传感器节点(RTC 唤醒 + DHT11 采集 + 功耗优化)
c++·stm32·单片机·嵌入式硬件·mcu·架构·硬件架构
ONE_SIX_MIX2 小时前
lancedb 表名 编解码 与 转译 python
开发语言·python
Rabbit_QL2 小时前
【Claude Code 循环登录】浏览器显示成功,CLI 永远 Not logged in
开发语言
晨非辰2 小时前
Linux终端输出哲学:从回车换行到进度条实战,掌握缓冲区刷新与ANSI控制,告别输出延迟焦虑
linux·运维·服务器·c++·人工智能·后端·自动化
C++ 老炮儿的技术栈2 小时前
Qt 开发机器人客户端程序
c语言·开发语言·c++·windows·qt·机器人