C++中类成员函数做为回调函数

注:与tr1::function对象结合使用,能得到更好的效果,详情见http://blog.csdn.net/this_capslock/article/details/38564719程序员

 

回调函数是基于C编程的Windows SDK的技术,不是针对C++的,程序员能够将一个C函数直接做为回调函数,可是若是试图直接使用C++的成员函数做为回调函数将发生错误,甚至编译就不能经过。 编程

普通的C++成员函数都隐含了一个传递函数做为参数,亦即“this”指针,C++经过传递一个指向自身的指针给其成员函数从而实现程序函数能够访问C++的数据成员。这也能够理解为何C++类的多个实例能够共享成员函数可是确有不一样的数据成员。因为this指针的做用,使得将一个CALLBACK型的成员函数做为回调函数安装时就会由于隐含的this指针使得函数参数个数不匹配,从而致使回调函数安装失败。函数

这样从理论上讲,C++类的成员函数是不能看成回调函数的。但咱们在用C++编程时总但愿在类内实现其功能,即要保持封装性,若是把回调函数写做普通函数有诸多不便。通过网上搜索和本身研究,发现了几种巧妙的方法,可使得类成员函数看成回调函数使用。this

这里采用Linux C++中线程建立函数pthread_create举例,其原型以下:spa

 

[cpp]  view plain  copy
 
 print?在CODE上查看代码片派生到个人代码片
  1. int pthread_create( pthread_t *restrict tidp , const pthread_attr_t *restrict attr , void* (*start_rtn)(void*) , void *restrict arg );  

 

第一个参数为指向线程标识符的指针。
第二个参数用来设置线程属性。
第三个参数是线程运行函数的起始地址,即回调函数。
最后一个参数是运行函数的参数。
 
这里咱们只关注第三个参数start_run,它是一个函数指针,指向一个以void*为参数,返回值为void*的函数,这个函数被看成线程的回调函数使用,线程启动后便会执行该函数的代码。
 
方法一:回调函数为普通函数,但在函数体内执行成员函数
见如下代码:
[cpp]  view plain  copy
 
 print?在CODE上查看代码片派生到个人代码片
  1. class MyClass  
  2. {  
  3.     pthread_t TID;  
  4. public:  
  5.     void func()  
  6.     {  
  7.         //子线程执行代码  
  8.     }  
  9.   
  10.     bool startThread()  
  11.     {//启动子线程  
  12.         int ret = pthread_create( &TID , NULL , callback , this );  
  13.         if( ret != 0 )  
  14.             return false;  
  15.         else  
  16.             return true;  
  17.     }  
  18. };  
  19.   
  20. static void* callback( void* arg )  
  21. {//回调函数  
  22.     ((MyClass*)arg)->func();调用成员函数  
  23.     return NULL;  
  24. }  
  25.   
  26. int main()  
  27. {  
  28.     MyClass a;  
  29.     a.startThread();  
  30. }  

 

类MyClass须要在本身内部开辟一个子线程来执行成员函数func()中的代码,子线程经过调用startThread()成员函数来启动。这里将回调函数callback写在了类外面,传递的参数是一个指向MyClass对象的指针(在pthrad_create()中由第4个参数this指定),回调函数通过强制转换把void*变为MyClass*,而后再调用arg->func()执行子线程的代码。.net

这样作的原理是把当前对象的指针看成参数先交给一个外部函数,再由外部函数调用类成员函数,之外部函数做为回调函数,但执行的是成员函数的功能,这样至关于在中间做了一层转换。缺点是回调函数在类外,影响了封装性,这里把callback()限定为static,防止在其它文件中调用此函数。线程

 

方法二:回调函数为类内静态成员函数,在其内部调用成员函数指针

在方法一上稍做更改,把回调函数搬到类MyClass里,这样就保持了封装性。代码以下:rest

 

[cpp]  view plain  copy
 
 print?在CODE上查看代码片派生到个人代码片
  1. class MyClass  
  2. {  
  3.     static MyClass* CurMy;//存储回调函数调用的对象  
  4.     static void* callback(void*);//回调函数  
  5.     pthread_t TID;  
  6.     void func()  
  7.     {  
  8.         //子线程执行代码  
  9.     }  
  10.       
  11.     void setCurMy()  
  12.     {//设置当前对象为回调函数调用的对象  
  13.         CurMy = this;  
  14.     }  
  15. public:  
  16.     bool startThread()  
  17.     {//启动子线程  
  18.         setCurMy();  
  19.         int ret = pthread_create( &TID , NULL , MyClass::callback , NULL );  
  20.         if( ret != 0 )  
  21.             return false;  
  22.         else  
  23.             return true;  
  24.     }  
  25. };  
  26. MyClass* MyClass::CurMy = NULL;  
  27. void* MyClass::callback(void*)  
  28. {  
  29.     CurMy->func();  
  30.     return NULL;  
  31. }  
  32.   
  33. int main()  
  34. {  
  35.     MyClass a;  
  36.     a.startThread();  
  37. }  

类MyClass有了1个静态数据成员CurMy和1个静态成员函数callback。CurMy用来存储一个对象的指针,充当方法一中回调函数的参数arg。callback看成回调函数,执行CurMy->func()的代码。每次创建线程前先要调用setCurMy()来让CurMy指向当前本身。code

 

这个方法的好处时封装性获得了很好的保护,MyClass对外只公开一个接口startThread(),子线程代码和回调函数都被设为私有,外界不可见。另外没有占用callback的参数,能够从外界传递参数进来。但每一个对象启动子线程前必定要注意先调用setCurMy()让CurMy正确的指向自身,不然将为其它对象开启线程,这样很引起很严重的后果。

 

方法三:对成员函数进行强制转换,看成回调函数

代码以下:

 

[cpp]  view plain  copy
 
 print?在CODE上查看代码片派生到个人代码片
  1. class MyClass  
  2. {  
  3.     pthread_t TID;  
  4.     void func()  
  5.     {  
  6.         //子线程执行代码  
  7.     }  
  8. public:  
  9.     bool startThread()  
  10.     {//启动子线程  
  11.         typedef void* (*FUNC)(void*);//定义FUNC类型是一个指向函数的指针,该函数参数为void*,返回值为void*  
  12.         FUNC callback = (FUNC)&MyClass::func;//强制转换func()的类型  
  13.         int ret = pthread_create( &TID , NULL , callback , this );  
  14.         if( ret != 0 )  
  15.             return false;  
  16.         else  
  17.             return true;  
  18.     }  
  19. };  
  20.   
  21. int main()  
  22. {  
  23.     MyClass a;  
  24.     a.startThread();  
  25. }  

这个方法是原理是,MyClass::func最终会转化成 void func(MyClass *this); 也就是说在原第一个参数前插入指向对象自己的this指针。能够利用这个特性写一个非静态类成员方法来直接做为线程回调函数。对编译器而言,void (MyClass::*FUNC1)()和void* (*FUNC)(void*)这两种函数指针虽然看上去很不同,但他们的最终形式是相同的,所以就能够把成员函数指针强制转换成普通函数的指针来看成回调函数。在创建线程时要把当前对象的指针this看成参数传给回调函数(成员函数func),这样才能知道线程是针对哪一个对象创建的。

 

方法三的封装性比方法二更好,由于不涉及多个对象共用一个静态成员的问题,每一个对象能够独立地启动本身的线程而不影响其它对象。

 

暂时就列出这些方法,之后发现更好的再来补充,over

http://blog.csdn.net/this_capslock/article/details/17001003

---------------------------------------------------------------------------------------

4楼  eziowayne 2015-04-17 15:38发表 [回复]
我的推荐第一种方法,第二种方法适用面窄,第三种是错误的方法,千万不能这么用。
3楼  __JacHan 2014-04-28 14:44发表 [回复]
用的时候用楼主的方法三实在调不通,论坛里面有人这样解释的:为了实现回调,咱们必须把this指针给转换掉!可为了在该函数中能够直接操做该类中的成员,咱们必须保留this指针!因此这是矛盾的,经过转换指针的手段是不能达到目的的! 来自:http://bbs.csdn.net/topics/360051927 仍是用tr1::function/bind实现了
相关文章
相关标签/搜索