Fork me on GitHub

C++之回调函数

1. c++之回调函数

1.1. 回调函数

回调函数就是在调用某个函数B时,将自己的一个函数A的地址作为参数传递给函数B,函数B在执行的过程中,通过那个函数指针参数反过来调用函数A,即被调用者回头调用调用者的函数,故其称为回调函数(callback function)。

比如一般的API提供方无法知道用户会怎样使用数据,它就会让用户自己设计处理数据的函数,并将该函数的地址作为参数传递给API接口,API接口获取数据后调用传递给它的函数来实现用户自定义的数据处理方式。即用户调用服务方的API接口,它又反过来调用用户自定义的函数。

1.2. c++中回调函数的使用

C语言中可像普通函数一样使用回调函数,只须把回调函数定义为全局的即可。而在C++中的类成员函数中使用回调函数会发生错误。这是因为普通的C++成员函数都隐含了一个传递函数作为参数,即this指针,C++通过传递this指针给其成员函数从而实现成员函数的调用(每个成员函数都需要一个对象去调用它)。由于this指针的作用,使得将一个callback型的成员函数作为回调函数时就会因为隐含的this指针使得函数参数个数不匹配,从而导致回调函数调用失败。(我的理解是:如果在API提供方提供的函数中调用类的成员函数,必需传递一个类的实例的指针,通过这个类的实例指针来调用该成员函数,否则在类外无法在没有类实例的情况下直接调用其成员函数)。

要解决这一问题,有三种方法:

1. 不使用成员函数,即将回调函数定义为全局的,像C语言一样使用回调函数(这样有个问题就是无法访问类的成员变量):

代码示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <iostream>
using namespace std;

typedef void (*pFun)(int);

// 假设是 API 接口
void fun(pFun fun, int a){
fun(a);
}

//---------------------------------
// 这个是回调函数
void callbackFun(int a){
cout << "callbackFun: " << a << endl;
}

class testClass{
public:
void start(pFun f, int a){
fun(f, a);
}
private:
};

int main(){
fun(callbackFun, 10); // C 的用法

testClass aaa;
aaa.start(callbackFun, 6); // 在类中调用全局的回调函数
return 1;
}

代码输出:

1
2
callbackFun: 10
callbackFun: 6

2. 不使用成员函数,为了访问类的成员变量,使用友元操作符(friend),在C++中将该函数说明为类的友元即可。

代码示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <iostream>
using namespace std;

typedef void (*pFun)(int);
void callbackFun(int); // 回调函数声明

// 假设是 API 接口
void fun(pFun fun, int a){
fun(a);
}

// 测试类
class testClass{
public:
testClass(int a) : m(a) {}
friend void callbackFun(int a); // 友元
private:
int m;
};

//---------------------------------
// 这个是回调函数
void callbackFun(int a){
testClass aaa(a);
cout << "callbackFun aaa.m: " << aaa.m << endl; // 访问类的私有成员
}

int main(){
fun(callbackFun, 10);
return 1;
}

代码输出:

1
callbackFun aaa.m: 10

3. 回调函数必需是类的成员函数,将其设计为静态成员函数,静态成员函数不使用this指针作为隐含变量,这样就可以作为回调函数了。但这样有一个严重的缺点:静态成员函数只能访问类的静态成员变量和静态成员函数,不能访问非静态的。可以通过设计一个静态类指针作为类成员来解决,通过在类创建时初始化该静态指针,然后在回调函数中通过该静态指针来访问所有成员变量和成员函数了。

代码示例如下:

{.line-numbers}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <iostream>
using namespace std;

typedef void (*pFun)(int);

// 假设是 API 接口
void fun(pFun fun, int a){
fun(a);
}

class testClass{
public:
testClass(int a) : m(a) {}

// 回调函数
static void callbackFun(int a){
cout << "callbackFun a: " << a << endl;
cout << "m: " << pTestClass->m << endl;
}

void start(pFun f, int a){
pTestClass = this; // 将该this指针赋给静态类指针
fun(f, a);
}
private:
int m;
static testClass* pTestClass; // 静态类指针
};

testClass* testClass::pTestClass = NULL;

int main(){
testClass aaa(10);
aaa.start(testClass::callbackFun, 6);
return 1;
}

代码输出:

1
2
callbackFun a: 6
m: 10

但是上述方法只能适用于一个实例的情况,因为多个实例将共享静态成员变量。为了避免这种情况,可以使用回调函数的一个参数来传递this指针,从而实现数据成员共享。