上一篇说了导出的C动态库的注意事项,这篇讲一个具体的问题。
在C的动态库中会有一些回调函数作为参数,这时候很多C++的coder就很头大,直接传类的成员函数会报错,用bind转一下也会报错,网上查资料也不少那么好找,这就讲一下这个问题。
1.回调函数的意义
下面是一个比较通俗易懂的回调函数的定义和使用场景
/*
你到一个商店买东西,刚好你要的东西没有货,于是你在店员那里留下了你的电话,过了几天店里有货了,店员就打了你的电话,然后你接到电话后就到店里去取了货。在这个例子里,你的电话号码就叫回调函数,你把电话留给店员就叫登记回调函数,店里后来有货了叫做触发了回调关联的事件,店员给你打电话叫做调用回调函数,你到店里去取货叫做响应回调事件。回答完毕。
https://www.zhihu.com/question/19801131/answer/13005983
*/
2.回调函数的困惑
造成C++程序员困惑的根源是对回调函数的理解有误。首先C的回调函数相对于C++是一个更狭隘的概念。C的回调函数简单来说就是一个指针,他指向一个函数地址。这就是造成困惑的关键,C风格的回调函数必须是有地址的函数,什么样的函数有函数地址,静态成员函数和全局函数都是有地址的。C++类的成员函数有函数地址吗?答案是否定的,类被实例化之后成员函数才会有具体的地址。
class A
{
public:
A(){};
void function()
{
}
}
A a;
a.function;
A::function是取不到这个函数的,因为这时函数只有声明没有地址,下面实例化的a才能取到function。
3.C与C++回调函数的区别
前面说到C的回调函数是比较狭隘的定义,一般是void (function*)()这种格式。在编译期就能确定找到函数地址。比如全局函数,比如静态成员变量。比如将function声明为static void function();通过A::function就可以找到这个函数的地址。
C++的回调函数更宽泛一点。运行时能找到的函数地址也可以当作回调函数,这里面就包含了类的普通成员函数。举个例子:C++中我们一般用bind来传函数指针,std::bind( &A::function,&a); bind的参数不仅不仅要传函数名还要传类的指针,这样才能唯一确定一个的函数地址。
4.C回调的一点建议:上下文指针
C的回调对于常用C++的程序员来说,用起来有点掣肘,虽然可以将类的静态成员函数当作参数传给C的回调函数,但是静态成员函数中无法调用非静态成员变量,用起来很不方面,所以一个上下文指针就很关键。这也是一个C风格回调函数的默认的一个规则,回调函数的最后一个参数一般为为void* context,这个上下文在别的传进去,每次回调触发都回带回来,一般传一个类的指针,这样就可以在静态成员变量里调用类的普通成员变量和普通成员函数。
class A
{
public:
A(){};
static void function(void* context)
{
// 通过上下文指针可以解决静态成员函数中无法调用非静态成员函数和变量的问题
A* a = reinterpret_cast<A*>(context);
a.aa = 1;
}
int aa = 0;
}
// C风格的回调函数有一个约定俗成的规则,带一个void*类型的参数
void (*Callback)(void* context);
int main()
{
A a;
Callback = a.function;
Callback(&a);
}