回调函数:将函数指针/可调用对象作为参数传入另一个函数,在特定时机(如异步完成、事件触发、遍历结束)被被动调用的函数,核心是解耦逻辑、实现灵活扩展。
一、核心分类(C++ 3种主流实现)
| 实现方式 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 函数指针 | 纯C兼容、简单回调 | 性能最高、无依赖 | 不支持类成员、无状态 |
| 函数对象(仿函数) | 带状态的回调 | 可存储数据、灵活 | 代码稍繁琐 |
| std::function + Lambda | 现代C++、复杂场景 | 支持Lambda/成员函数、易用 | 有轻微性能开销 |
二、代码示例(从基础到现代C++)
1. 基础版:函数指针(C风格,最底层)
原理:用 函数指针 接收回调地址,在目标函数中调用。
#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() 的类对象,可存储成员变量(状态)。
#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 匿名函数简化写法,支持捕获外部变量。
#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、函数指针、仿函数)。
#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,写法更简洁:
// C++20 简写模板
void processData(int data, auto&& callback) {
int result = data * 2;
callback(result);
}
4. 实战版:类成员函数作为回调
场景:回调逻辑封装在类中,需绑定 this 指针。
#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
三、核心应用场景(必看)
- 异步编程:线程完成后回调通知(如 std::thread 配合回调)
- 事件驱动:按钮点击、网络请求完成(Qt信号槽本质是回调)
- 遍历/算法: std::sort 自定义比较、 std::for_each 遍历处理
- 框架设计:SDK暴露回调接口,用户自定义逻辑(如日志、错误处理)
四、关键注意事项
- 空指针检查:回调前判断指针是否为空,避免崩溃
- 生命周期:回调对象/函数需在调用时有效(避免野指针)
- 性能:高频回调优先用函数指针/仿函数, std::function 有轻微开销
- 线程安全:多线程回调需加锁,避免数据竞争
转载自CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/2301_80136889/article/details/159359585



