在C里面,经常需要提供一个函数地址,注册到结构里,然后在程序执行到特定阶段时,回调该函数。创建线程,注册线程运行的主函数就是一个典型的例子。这里以简单的回调实例,说明C++中回调函数为成员函数时有关this指针的问题。由于C++对C的继承关系,C++没有自己的线程封装技术,一般而言我们创建线程时,还是用C的回调函数机制。类似的例子也挺多的。在Java等纯粹的面向对象语言,则不一样,不光有自己的独立的线程类型,对于回调,也是注册整个对象,而不是注册一个方法,如常用的观察者模式。这里,在网上查阅了大量关于this指针、类成员函数和静态成员函数的相关知识点,结合自己的理解作一些总结。 关于回调函数,类的成员函数作为回调函数,一般而言大家已经形成了编程范式,讨论一些生僻的用法,可能被认为是腐朽的,无价值的。这里只想客观分析一下技术点,思想可能在类似的场景中遇到也说不准。 通常我们理解的成员函数和this指针是: 《深入探索C++对象模型》中提到成员函数时,当成员函数不是静态的,虚函数,那么我们有以下结论: (1) &类名::函数名 获取的是成员函数的实际地址; (2) 对于函数x来讲obj.x()编译器转化后表现为x(&obj),&obj作为this指针传入; (3) 无法通过强制类型转换在类成员函数指针与其外形几乎一样的普通函数指针之间进行有效的转换。 通常我们理解的是类的普通成员函数有一个隐藏的参数,即第一个参数,其值是this。如果希望一个成员函数既能访问类的数据成员,又能作为回调函数,有如下几种方法: 1、静态成员函数作为回调函数 为了不失封装性,可以将需要作为回调的函数声明为静态的。静态的成员函数,可以直接在类的外部调用。我们知道静态成员函数是不能直接访问类的非静态数据和接口的。那么此时需要知道具体的对象地址或者引用才能访问具体的对象成员。又有两个方法能实现这个: 1)将对象的地址用全局变量记录,在静态成员函数中通过该全局变量访问数据成员和方法。来看具体的代码实例: #include <stdio.h> typedef void (*func)(void*); class CallBack; CallBack* g_obj = NULL; class CallBackTest ~CallBackTest() } void registerProc(func fptr, void* arg = NULL) void doCallBack() private: }; class CallBack ~CallBack() } static void display(void* p) private: }; int main(int argc, char** argv) return 0; #include <stdio.h> typedef void (*func)(void*); class CallBack; class CallBackTest } ~CallBackTest() } void registerProc(func fptr, void* arg = NULL) void doCallBack() private: }; class CallBack ~CallBack() } static void display(void* _this = NULL) private: }; int main(int argc, char** argv) return 0; pc->a++; 2、非静态成员函数作为回调函数 既然我们知道,非静态成员函数有一个隐藏的参数,那么能否注册的时候,多传入一个参数,然后隐藏的那个指向对象的参数默认就转为this指针的值了,相当于在调用时给this赋值。可以做一个尝试,代码如下: #include <stdio.h> typedef void (*func)(void*); class CallBack; class CallBackTest } ~CallBackTest() } void registerProc(func fptr, void* arg = NULL) void doCallBack() private: }; class CallBack ~CallBack() } void display() private: }; int main(int argc, char** argv) return 0; 使用静态成员函数加上参数传入this指针的方式应该说是目前比较完善的解决办法。不失封装性,又不失易用性。 |