C++成员函数如何当作回调函数同时传递this指针
作者:易拉罐里的人
就我目前了解所知,有三种函数可以作为回调函数:
- 1.普通函数
- 2.静态函数(我用得少没有写,直接跳过)
- 3.成员函数
1.普通函数作为注册函数
普通函数作为回调函数,比较简单,只要函数签名(返回值类型+参数类型)一致就可以了。
因为普通函数不是类成员函数,如果想要访问类成员,在执行回调函数的时候,要把对象指针传给回调函数,如下代码:
namespace yy0 { //普通全局函数 void call_back(void* pointer); class A { public: A() { //初始化指针 p_call_back = NULL; //单数最大的数 num = 9; } ~A() {} public: //打印这个数来验证是否正常调用回调函数 int num; private: //指向回调函数的地址的指针 void(*p_call_back)(void*); public: //用于注册回调函数 void register_call_back(void(*p)(void*)) { if (p) p_call_back = p; } //执行回调函数 void run_call_back() { if (p_call_back) { //把对象指针传递出去 p_call_back(this); } } //测试函数 void test() { //注册 register_call_back(call_back); //执行 run_call_back(); } }; void call_back(void* pointer) { if (pointer) { //需要进行指针转换 A* p = (A*)pointer; cout << "打印的值:" << p->num; } } } int main() { yy0::A a; a.test(); getchar(); return 0; }
结果正确打印,说明回调函数正常调用:
也可以定义一个全局的类对象指针:
namespace yy0 { //前置声明 class A; //普通全局函数 void call_back(); //全局的类对象指针 A* pointer = NULL; class A { public: A() { //给全局类对象指针赋值 pointer = this; //初始化指针 p_call_back = NULL; //单数最大的数 num = 9; } ~A() {} public: //打印这个数来验证是否正常调用回调函数 int num; private: //指向回调函数的地址的指针 void(*p_call_back)(); public: //用于注册回调函数 void register_call_back(void(*p)()) { if (p) p_call_back = p; } //执行回调函数 void run_call_back() { if (p_call_back) { //把对象指针传递出去 p_call_back(); } } //测试函数 void test() { //注册 register_call_back(call_back); //执行 run_call_back(); } }; void call_back() { if (pointer) { //需要进行指针转换 A* p = (A*)pointer; cout << "打印的值:" << p->num; } } }
这也可以正确执行,但是这种定义全局的对象指针有风险。如果只创建一个A的对象,就可以正常使用,不会出现什么太大问题。但是,一旦创建的对象个数≥2,那么就造成数据读取错误的问题。
可以想象一下,创建对象a1时,全局对象指针pointer是指向a1的位置,那么读取的pointer->num,是a1对象的num。
然后再创建a2,那么全局对象指针pointer就变成了指向a2的位置(因为pointer是个全局变量,从始至终只有一个这个变量),那么执行a2.text(),pointer->num读取的是a2的num。
如果执行a1.text(),那么此时,pointer->num读取的也是a2的num,而不是a1的num。更严重的是,一旦删除了a1或者a2,就会造成另外一个对象访问内存失败的问题。
2.静态函数作为注册函数
这个就自行上网查看吧,我用的少就不写了。
3.成员函数作为注册函数
假设场景:A类成员函数作为B类回调函数
《深度探索C++对象模型》这本书讲到,类成员函数都有一个隐藏参数用于传递this指针,这个this传递给函数由编译器来完成,不需要用户来做。
直接上代码:
namespace yy3 { class B { public: B() { pointer = NULL; } ~B() {} public: //存放A类的this指针 void* pointer; //指向回调函数 void(__stdcall *pCallBack)(void*); public: /* @函数作用:注册回调 @输入参数: void(*p)(void*) -- 输入A类的回调函数的地址 void* p_this -- 输入A类的this指针 */ //② void register_fun(void(__stdcall *p)(void*), void* p_this) { pCallBack = p; pointer = p_this; } //执行回调 //③ void run_call_back() { if (pCallBack) pCallBack(pointer); } }; class A { public: A() { a = 5; } A(int num) { a = num; }; ~A() {} public: //在A类中定义一个B类的变量 B b; //拿来测试的变量 double a; //定义联合,不知道原理,网上查到的技巧 union for_callback { void(__stdcall *fun_int_c)(void*); void (A::*fun_in_class)(void*); }fp; public: //要拿来注册的回调函数 void call_back(void* p) { A* pointer = (A*)p; //能打印出正确的a值就对了 cout << "a:" << pointer->a << endl; } //测试函数 //① void test() { fp.fun_in_class = &A::call_back; b.register_fun(fp.fun_int_c, this); b.run_call_back(); } }; } int main() { yy3::A a; a.test(); getchar(); return 0; }
首先来解释一地方
1.__stdcall声明:这个看情况,我在公司电脑写的时候不需要加这个关键字,自己的电脑就要加这个。就是一个传参约定,可以上网查。
2.
//定义联合,不知道原理,网上查到的技巧 union for_callback { void(__stdcall *fun_int_c)(void*); void (A::*fun_in_class)(void*); }fp;
使用union,这个说是为了逃避编译器检查,原理我也不太懂,如果有知道原理的大神,麻烦告诉一下下,感谢感谢。我直接就拿来用了。
3.前面说了成员函数有个隐含传递指针的参数,所以函数指针:
//指向回调函数 void(__stdcall *pCallBack)(void*);
需要定义参数为void*的函数指针,用于传递A类的this指针
4.因为函数指针是B类的成员,而函数指针接受的参数是A类的this指针,我们不能直接这样使用:
void run_call_back() { if (pCallBack) pCallBack(this); }
这个pCallBack(this)中的this是指向B类对象的地址而非A类对象的地址,因此,在B类定义一个成员:void* pointr,用于保存A类对象的指针,然后这样使用
//执行回调 void run_call_back() { if (pCallBack) pCallBack(pointer); }
这样就运行回调函数,同时传递A类对象指针。
5.(无参这一点单独在这里说)当然,虽然成员函数有自带隐藏参数,我们也可以把它转换成无参的函数,修改这些地方:
//【1】 //指向回调函数 void(__stdcall *pCallBack)(void*); //修改为 void(__stdcall *pCallBack)(); //【2】 void register_fun(void(__stdcall *p)(void*), void* p_this) { pCallBack = p; pointer = p_this; } //修改为 void register_fun(void(__stdcall *p)(), void* p_this) { pCallBack = p; pointer = p_this; } //【3】 //执行回调 void run_call_back() { if (pCallBack) pCallBack(pointer); } //修改为 void run_call_back() { if (pCallBack) pCallBack(); } //【4】 union for_callback { void(__stdcall *fun_int_c)(void*); void (A::*fun_in_class)(void*); }fp; //修改为 union for_callback { void(__stdcall *fun_int_c)(); void (A::*fun_in_class)(); }fp; //【5】 //要拿来注册的回调函数修改为 void call_back() { cout << "a:" << this->a << endl; }
这种情况编译能通过,但是void call_back()使用this指针,是无法正确读取内存的值,如下
言归正传。
成员函数转为带一个void*参数的函数运行情况如下:
结果也是一个不正确的值,因此进行调试查看,把断点放在这个函数上,发现了一个奇怪的问题:
//要拿来注册的回调函数 void call_back(void* p) { A* pointer = (A*)p; //能打印出正确的a值就对了 cout << "a:" << pointer->a << endl; }
pointer是A类对象的指针,pointer通过函数指针pCallBack(pointr)传递给了call_back(void* p),从理论上讲,p的值要与pointer保持一致才对。但是p的值与pCaalBack相同,也就是p是函数指针,特别奇怪。我也不知道什么原因,所以如果有人知道,麻烦跟我讲一下,在这里先谢谢了。
我无法解决这个问题,所以尝试了将函数指针转为带有两个void*参数的函数,竟然可以传递正确的this指针,算是瞎猫碰上死耗子,代码跟上面类似,如下:
namespace yy3 { class B { public: B() { pointer = NULL; } ~B() {} public: //存放A类的this指针 void* pointer; //指向回调函数 void(__stdcall *pCallBack)(void*, void*); public: /* @函数作用:注册回调 @输入参数: void(*p)(void*,void*) -- 输入A类的回调函数的地址 void* p_this -- 输入A类的this指针 */ void register_fun(void(__stdcall *p)(void*, void*), void* p_this) { pCallBack = p; pointer = p_this; } //执行回调 void run_call_back() { if (pCallBack) //需要两个指针作为参数,干脆就传递两个pointer吧 pCallBack(pointer,pointer); } }; class A { public: A() { a = 5; } A(int num) { a = num; }; ~A() {} public: //在A类中定义一个B类的变量 B b; //拿来测试的变量 double a; //定义联合,不知道原理,网上查到的技巧 union for_callback { void(__stdcall *fun_int_c)(void*, void*); void (A::*fun_in_class)(void*, void*); }fp; public: //要拿来注册的回调函数 void call_back(void* p, void* pp) { A* pointer = (A*)p; //能打印出正确的a值就对了 cout << "a:" << pointer->a << endl; } //测试函数 void test() { fp.fun_in_class = &A::call_back; b.register_fun(fp.fun_int_c, this); b.run_call_back(); } }; }
结果是正确的:
从图上可知,pCallBack(函数指针)的值,与p和pp都不同,无论是p还是pp,这两个值都是A类对象的地址,也就是说,已经成功把A的this指针传递进来了。因此结果也是正确的。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。