【轉】回調函數中如何調用類中的非靜態成員變量或非靜態成員函數

回調函數中調用類中的非靜態成員變量或非靜態成員函數程序員

【問題1】如何在類中封裝回調函數?編程

【答】:
  a.回調函數只能是全局的或是靜態的。
  b.全局函數會破壞類的封裝性,故不予採用。
  c.靜態函數只能訪問類的靜態成員,不能訪問類中非靜態成員。
 
 【問題2】如何讓靜態函數訪問類的非靜態成員?
    【解決方案】:函數

    聲明一靜態函數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

=================================spa

 

C++中類成員函數做爲回調函數

 

 

 

回調函數是基於C編程的Windows SDK的技術,不是針對C++的,程序員能夠將一個C函數直接做爲回調函數,可是若是試圖直接使用C++的成員函數做爲回調函數將發生錯誤,甚至編譯就不能經過。 .net

普通的C++成員函數都隱含了一個傳遞函數做爲參數,亦即「this」指針,C++經過傳遞一個指向自身的指針給其成員函數從而實現程序函數能夠訪問C++的數據成員。這也能夠理解爲何C++類的多個實例能夠共享成員函數可是確有不一樣的數據成員。因爲this指針的做用,使得將一個CALLBACK型的成員函數做爲回調函數安裝時就會由於隱含的this指針使得函數參數個數不匹配,從而致使回調函數安裝失敗。線程

這樣從理論上講,C++類的成員函數是不能看成回調函數的。但咱們在用C++編程時總但願在類內實現其功能,即要保持封裝性,若是把回調函數寫做普通函數有諸多不便。通過網上搜索和本身研究,發現了幾種巧妙的方法,可使得類成員函數看成回調函數使用。指針

這裏採用Linux C++中線程建立函數pthread_create舉例,其原型以下:rest

 

[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()執行子線程的代碼。code

這樣作的原理是把當前對象的指針看成參數先交給一個外部函數,再由外部函數調用類成員函數,之外部函數做爲回調函數,但執行的是成員函數的功能,這樣至關於在中間做了一層轉換。缺點是回調函數在類外,影響了封裝性,這裏把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),這樣才能知道線程是針對哪一個對象創建的。

 

方法三的封裝性比方法二更好,由於不涉及多個對象共用一個靜態成員的問題,每一個對象能夠獨立地啓動本身的線程而不影響其它對象。

相關文章
相關標籤/搜索