在C语言中,回调函数是一个非常重要的概念,它的定义
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
其实说白了就是把一个函数当做参数传下去。
C中实现回调函数比较简单,请看下面这个例子:
#include
typedef void (*p_CB)(int);
void CB(int a) { printf("CB a = %d\n", a); }
void runCB(p_CB CB, int a) { printf("run CB\n"); CB(a); }
int main()
{
p_CB cb = &CB;
runCB(cb, 20);
return 0;
}
在C++中的一个重要概念就是类,所以我们一般想让类的成员函数作为回调函数(如果直接用非类的成员函数作为回调函数,其实就和C语言中的方法一样),但是想实现这样的功能,还是存在一些限制的。主要原因是,类的成员函数的参数都隐藏了this指针这个参数,既然多加了一个参数,那么成员函数的参数个数就和定义的回调函数个数不匹配,导致调用失败。
举个例子:
#include
typedef void (*p_CB)(int);
void runCB(p_CB CB, int a) { printf("run CB\n"); CB(a); }
class B
{
public:
void CB(int a) { printf("CB a = %d\n", a); }
};
int main()
{
B b;
runCB(B::CB, 20); //错误
return 0;
}
我们这样写,在15行会报下面的错误。
test_class_cb.cpp: In function ‘int main()’:
test_class_cb.cpp:15:14: error: invalid use of non-static member function ‘void B::CB(int)’
runCB(B::CB, 20); //错误
报的错说使用了非静态的函数,分析下原因,其实是B::CB的函数是非静态成员函数,它隐藏了this参数,导致p_CB和B::CB这两个函数的参数不匹配。
而编译器报的这个错误,其实也是同样的意思,编译器说我们调用的不是非静态函数,当我们把B::CB定义成静态函数也就不会报错了(也就是去掉了this指针)。
既然成员函数都会存在this指针,那么我们可以去掉this指针呢?其实是可以的,我们把成员函数定义为静态函数,静态函数是属于整个类的,不属于某个对象,也就没有this指针了。
这个例子的话,就是我们在上一个章节提到的,大家可以直接加上static。
这种方法其实有一定的缺点,静态会带来诸多的不变,因为静态函数没有this指针,那么它就访问不了普通的成员函数或者数据成员,会给我们在开发中带来不小的影响,特别是工程比较大的时候。
这个方法从另外一个角度出发,我们可以定义一个成员函数的函数指针,其实相当于这个函数指针有了this参数了。
例子如下:
#include
class B
{
public:
void CB(int a) { printf("CB a = %d\n", a); }
};
typedef void (B::*p_CB)(int);
// using p_CB = void (B::*)(int);
void runCB(p_CB CB, B b, int a) { printf("run CB\n"); (b.*CB)(a); }
int main()
{
B b;
p_CB p_f1 = &B::CB;
runCB(p_f1, b, 20);
return 0;
}
这种写法晦涩难懂,特别是(b.*CB)(a)这种写法一看上去就觉得很奇怪,代码写的越生僻,越复杂,带来的风险就越大。另外,如果想要采用这种方法实现回调,那么在定义回调函数的时候就得知道谁会调用它,显然,这个要求是不合理的,所以,这种方法很不常用,只是介绍这种方法。
这个方法是上面静态函数方法的进一步优化,因为静态函数没有传进去this指针,那么我们可不可以想办法得到this指针呢?单例模式就可以做到,一般上单例模式为了让类只存在一个对象,一般都会在类的内部定义一个指向自己的指针,我们就可以通过这个指针,在任何地方都可以访问到这个对象。
简单例子:
#include
class B
{
public:
static B *get_instance() {
static B instance;
return &instance;
}
void CB(int a) { printf("CB a = %d\n", a); }
};
void runCB(int a)
{
printf("run CB\n");
B *b_ptr = B::get_instance();
b_ptr->CB(a);
}
int main(int argc, char *argv[])
{
B *b_ptr = B::get_instance();
runCB(20);
return 0;
}
使用这种方法,在任何地方都可以访问该对象,可以很方便得调用对象的方法,是比较好的一种方法了,需要注意的就是单例模式的一些限制而已。另外说一下,单例模式只能说是一种可以解决回调函数的办法,不一定是因为要回调,才设计成单例。
如果你的编译器支持C++11标准的话(现在编译器基本上都会支持),还有一种更加方便快捷的方法。
首先介绍一下什么叫可调用对象,简单来说就是可以使用()来调用的对象,在C++11中,包括函数、函数指针、lambda表达式、bind创建的对象和重载了()的类。
function是一个模板函数,它的功能和函数指针很像,但是函数指针只能针对非成员函数,而function则可以调用C++中所有的可调用实体。
对于成员函数来说,function不能直接调用,这就需要通过下面的bind来做“媒介”。
bind函数简单介绍,就是可以绑定到任意一个可调用对象,下面是它的定义。
可以讲bind看作一个通用的函数适配器,他接受一个可调用对象,生成一个新的可调用对象来“适应”原对象的参数列表。----《c++Primer》
也就是说,通过bind绑定类的非静态成员函数,生成一个可调用对象,然后通过function模板去调用这个可调用对象,也就实现了调用类的非静态成员函数。
下面通过一个简单的例子来说明如何使用。
例子
#include
#include
// typedef std::function funCB;
using funCB = std::function<void(int)>; //可以这样写,更直观
class B
{
public:
void setCB(funCB CB) { m_CB = CB; }
void runCB(int a) { std::cout << "runCB" << std::endl; m_CB(a); }
void printB(int a) { std::cout << "a = " << a << std::endl; }
funCB m_CB;
};
class C
{
public:
void cbfun(int a) { std::cout << "CB a = "
<< a << std::endl; }
};
int main()
{
B b; C c;
funCB fun = std::bind(&C::cbfun, &c, std::placeholders::_1);
b.setCB(fun);
b.runCB(20);
return 0;
}
在这个例子中,b.runCB(),然后会调用m_CB函数,而m_CB通过setCB绑定到了C::cbfun,所以C::cbfun也会被调用。也就是就是通过b的成函数调用到了c的成员函数,实现了回调。
这种方式是不是看起很简单呢,
这边文章介绍了在C++中实现回调的几种方法,总的来说C++11的function&bind是比较好的方法,另外说一点,function的bind的功能不止这些,这里介绍的只是他们的一种用法。