关注

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

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

一、核心分类(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

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

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

四、关键注意事项

  1. 空指针检查:回调前判断指针是否为空,避免崩溃
  2. 生命周期:回调对象/函数需在调用时有效(避免野指针)
  3. 性能:高频回调优先用函数指针/仿函数, std::function 有轻微开销
  4. 线程安全:多线程回调需加锁,避免数据竞争

转载自CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/2301_80136889/article/details/159359585

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--