文章

回调函数

回调函数

回调函数

C++ 中的 回调函数(Callback Function) 是一种把函数当作参数传递给另一个函数,并在特定时机调用它的机制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>

void onEvent() {
    std::cout << "Event triggered!" << std::endl;
}

// 回调接受者
void doSomething(void (*callback)()) {
    std::cout << "Doing something...\n";
    callback();  // 调用回调
}

int main() {
    doSomething(onEvent);  // 把函数传进去作为回调
    return 0;
}

核心要点

特性描述
“回调”本质把函数作为参数传递给另一个函数
触发时机由被调用函数决定(你控制不了调用时机,但提供了函数)
使用目的提高灵活性、控制反转(Inversion of Control)
实现方式函数指针、函数对象、Lambda、std::function

更现代写法(Lambda + std::function

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>
#include <functional>

void doSomething(std::function<void()> callback) {
    std::cout << "Doing something...\n";
    callback();  // 调用回调
}

int main() {
    doSomething([]() {
        std::cout << "Lambda callback triggered!" << std::endl;
    });
    return 0;
}

这里的 std::function<void()> 可以接收:

  • 函数指针
  • lambda 表达式
  • 函数对象(只要重载了 operator()

应用场景

  • 事件驱动编程:比如 UI 框架中点击按钮触发回调
  • 异步编程:线程完成任务后调用回调通知主线程
  • 策略模式:将不同策略函数传入算法中动态调用
  • 模拟信号与槽:类似 Qt 的 signal/slot 机制

对比不同的实现方式

方法是否支持状态是否可捕获变量是否可用作回调
函数指针
函数对象✅(通过成员)
Lambda(无捕获)
Lambda(捕获)✅(需要 std::function
std::function✅(包装)✅✅✅

为什么普通成员函数不能直接作为回调?

1. 函数调用的本质区别

  • 普通函数(包括全局函数和静态成员函数)在调用时,只有显式传入的参数。

    例如:

    1
    2
    
    void func(int x);
    func(10);  // 只传了一个参数 x = 10
    
  • 普通成员函数调用时,除了显式传入的参数外,还有一个“隐藏参数” —— this 指针,它指向调用该函数的对象。

    换句话说:

    1
    2
    3
    4
    5
    6
    7
    
    class MyClass {
    public:
        void memberFunc(int x);
    };
      
    MyClass obj;
    obj.memberFunc(10);  // 实际调用时会隐式传入 obj 的地址作为 this 指针
    

    在底层,这相当于调用:

    1
    
    memberFunc(&obj, 10);
    

    this 指针告诉函数它是在哪个对象上操作。

2. 函数指针类型不匹配

  • 普通函数指针类型

    1
    
    typedef void (*FuncPtr)(int);
    

    这表示指向普通函数的指针,调用时只传入一个 int 参数。

  • 普通成员函数指针类型

    1
    
    typedef void (MyClass::*MemFuncPtr)(int);
    

    这是成员函数指针,调用时还需要传入调用的对象实例(即 this 指针)。

3. 回调函数为什么用普通函数指针?

许多库或API(特别是C风格的)期望你传入一个普通函数指针作为回调,调用方式是:

1
callback(42);  // 只传入一个参数

但是普通成员函数调用需要:

1
(obj->*memberFunc)(42);  // 需要明确的对象实例 obj

这两种调用方式不一样,不能直接互换。

4. 结论

  • 普通成员函数隐式带了 this 参数,需要明确对象实例。
  • 但是回调函数调用时,库只知道要传入显式参数,不知道也不能传 this

    • “库”是调用你回调函数的那段代码,它不知道你想用哪个对象调用成员函数,因为你只给了它函数指针,它调用时也没有办法传 this 指针。所以你不能直接传普通成员函数当回调,必须用静态函数包一层或者用更高级的方式(如 std::function)。

  • 所以普通成员函数不能直接作为普通函数指针类型的回调函数

5. 解决方案示例

常用的做法是:

  • 用静态成员函数或全局函数作为回调,因为它们没有 this 参数;
  • 如果需要调用普通成员函数,则在静态成员函数里传入对象指针,再调用成员函数。
本文由作者按照 CC BY 4.0 进行授权