回调函数中如何调用类中的非静态成员变量或非静态成员函数

申明:本文非笔者原创,原文转载自:http://blog.csdn.net/bzhxuexi/article/details/19831667程序员

【问题1】如何在类中封装回调函数?编程

【答】:
  a.回调函数只能是全局的或是静态的。
  b.全局函数会破坏类的封装性,故不予采用。
  c.静态函数只能访问类的静态成员,不能访问类中非静态成员。
 
 【问题2】如何让静态函数访问类的非静态成员?
    【解决方案】:ide

    声明一静态函数a(),将类实例对象指针作为参数传入。如:
  class A()
  {
      static void a(A *); //静态函数
      void b();  //非静态函数 
  }  
  void A::a(A * pThis)
  {
   pThis->b(); //静态函数中调用非静态函数 
  }
     
 【问题3】回调函数中如何访问非静态成员?
  因为回调函数每每有固定定义,并不接受  A * pThis 参数
  如:CALLBACK MyTimerProc(HWND hwnd,UINT uMsg,UINT idEvent,DWORD dwTime);
  
        【解决方案1】:本方案当遇到有多个类实例对象时会有问题。缘由是pThis指针只能指向一个对象。
  class A()
  {
      static void a(); //静态回调函数
      void b();  //非静态函数 
      static A * pThis;   //静态对象指针
  }  
  
  A * A::pThis=NULL;
  A::A()   //构造函数中将this指针赋给pThis,使得回调函数能经过pThis指针访问本对象
  {
       pThis=this;
  }
  void A::a()
  {
      if (pThis==NULL) return;
      pThis->b(); //回调函数中调用非静态函数 
  }
  
  【解决方案2】:本方案解决多个类实例对象时方案1的问题。用映射表存全部对象地址,每一个对象保存本身的ID号。
  typedef CMap<UINT,UINT,A*,A*> CAMap;
  class A()
  {
      static void a(); //静态回调函数
      void b();  //非静态函数 
      int m_ID;  //本对象在列表中的ID号
      static int m_SID;   //静态当前对象ID        (须要时,将m_ID赋值给m_SID以起到调用本对象函数的功能)
      static CAMap m_Map; //静态对象映射表
  }  
  
  CAMap A::m_Map;
  int   A::m_SID=0;
  
 
  A::A()   //构造函数中将this指针赋给pThis,使得回调函数能经过pThis指针访问本对象
  {
      if(m_Map.IsEmpty())
      {
   m_ID=1;
      }
      else
      { 
        m_ID=m_Map.GetCount()+1;
      }
      m_Map.SetAt( m_ID, this );
  }
  void A::a()
  {
      if (m_Map.IsEmpty()) return;
      A * pThis=NULL;
      if(m_Map.Lookup(m_SID,pThis))
      {
   pThis->b(); //回调函数中调用非静态函数 
      };
  }函数

=================================this

 

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

 

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

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

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

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

[cpp]  view plain copy 在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 在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()执行子线程的代码。rest

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


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

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

[cpp]  view plain copy 在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指向当前本身。

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


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

代码以下:

[cpp]  view plain copy 在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),这样才能知道线程是针对哪一个对象创建的。

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