回调函数的理解


回调函数的基本定义

回调函数定义上来说,指的就是作为另外一个函数的参数的函数。

实现原理,可以简单概括为:

存在一个函数,它的入参中有一个函数指针(能传入函数地址即可)。
顺序执行函数内部代码时,在适合的时候使用这个函数指针,以达到希望的结果。
(比如,传出某些计算出来的数据,或者生成某些需要给其他调用者的信息等)

C语言中的回调函数大多都是 void*的形式存在,而void*指代的即是回调函数的函数指针。

如下示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int Sum(int a, int b)
{
return a+b;
}
//cbfunc,用以传入函数指针
void SomeFunc(void* cbfunc, int param1, int param2)
{
...
int result = cbfunc(param1, param2);
...
}

//调用
SomeFunc(Sum, 2, 5);

回调函数的作用

延迟执行

回调函数的这个用处,可以从上述代码中就能略知一二。
调用SomeFunc函数的时候传入了Sum方法,但是Sum方法的执行却需要在函数内部执行到对应代码的时候。

此外,我们平时使用的标准库的算法,大多也是处于这种目的来使用回调函数。

1
2
3
4
5
6
7
8
9
10
11
std::vector<std::string> vec{
"abcdef", "123456", "abcdef123456", "123456abcdef"};

std::for_each(vec.begin(), vec.end(),
[](const std::string &str)
{
if (str.length() > 10)
{
std::cout << str << '\n';
}
});

标准库的算法,大多都是类似上述的方法,通过Lambda函数作为回调函数的形式来完成使用。


分离调用者和被调用者

回调函数可以使调用者不需要关心谁是被调用者,
只需要知道,存在一个具有某种特定原型、某些限制条件的被调用函数。

回调函数的这个作用,通常可用于不同模块之间的异步数据通知。

而怎么使用回调函数达到这个设计,在C++中有多种模式,除了上边已经提到的函数指针和lambda函数,还可以使用函数模板std::funciton 或者纯虚抽象类的形式。

std::function

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
37
38
39
class Callback
{
public:
void OnResponse(const char *data, int len)
{
cout << "Calling OnResponse :";
cout << std::string(data, len) << endl;
}

Callback() = default;
~Callback() = default;
};

typedef std::function<void(const char *, int)> OnRspFuncType;

class Client
{
public:
void RegisterCallback(OnRspFuncType func)
{
onRspFunc_ = std::move(func);
}

void Running()
{
for (int i = 0; i < 100; ++i)
{
this_thread::sleep_for(chrono::milliseconds(i));
if (i % 20 == 5)
{
std::string content = fmt::format("SendBack: {}", i);
onRspFunc_(content.data(), (int)content.length());
}
}
}

private:
OnRspFuncType onRspFunc_;
};

上述例子使用c++11的 std::function 来定义回调函数的类型。
std::function 配合 std::bind使用,可以达到回调函数的参数传递的目的。
相对直接使用函数指针,这么使用至少可以保证类型安全,且编译器的检查可以确保调用正确。

使用上述回调类的代码如下:

1
2
3
4
5
6
7
void Test()
{
Callback cb;
Client cli;
cli.RegisterCallback(std::bind(&Callback::OnResponse, &cb, placeholders::_1, placeholders::_2));
cli.Running();
}

纯虚抽象类

当然,除了上述的 函数指针、lambda函数、std::function这些,我们平时比较常见的使用回调函数的形式是纯虚的抽象类接口的形式。

举个例子来说明下纯虚类对回调函数的使用:

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
//纯虚抽象类定义
class CallbackInterface
{
public:
virtual void OnResponse(const char *data, int len){}
virtual ~CallbackInterface(){}
};

//被调用者继承抽象类,实现具体回调
class Callback : public CallbackInterface
{
public:
void OnResponse(const char *data, int len)
{
cout << "Calling OnResponse :";
cout << std::string(data, len) << endl;
}

Callback() = default;
~Callback() = default;
};

//调用者代码
class Client
{
public:
void RegisterCallback(CallbackInterface* cb)
{
cb_ = cb;
}

//模拟使用回调函数
void Running()
{
for (int i = 0; i < 100; ++i)
{
this_thread::sleep_for(chrono::milliseconds(i));
if (i % 20 == 5)
{
std::string content = fmt::format("SendBack: {}", i);
cb_->OnResponse(content.data(), (int)content.length()); // error
}
}
}

private:
CallbackInterface *cb_;
};

void Test()
{
Client cli;
CallbackInterface* cb = new Callback;
cli.RegisterCallback(cb);
cli.Running();
}

这里这个回调类只声明了一个回调函数,因此可能感觉使用纯虚类有一点迂回。
但如果是需要多个回调函数的场景,使用这种方式的话,就比较简便了,(因为注册的调用只需要一次即可)。


最后补充一点,C++里的纯虚函数,在Java里其实就是Interace,顾名思义,够明显了。