{譯}函數指針漫談(3)

在C/C++中如何實現callback

http://blog.csdn.net/luckheadline
1. callback函數概念
    函數指針提供了callback函數的概念。如果你不確信如何使用函數指針,請參見這系列前面的部分。我通過著名的排序函數qsort來介紹callback函數的概念。這個函數將根據用戶指定的rank規則對一個域中的元素進行排序。這個域可以包含任何類型的元素;它使用void指針傳遞給sort函數。元素的大小和元素的數量也要傳遞給函數。現在的問題是:排序函數在沒有元素相關信息的情況下如何排序?答案很簡單:函數將接受一個比較函數(有兩個void指針元素作爲參數)的指針作爲參數,得到它們的rank然後返回一個int型結果。因此排序算法需要決定如何rank兩個元素,它只是通過函數指針調用比較函數。這就是callback!

2. 在C語言中如何實現callback
    這裏就拿qsort函數的聲明舉例:
void qsort(void* field, size_t nElements, size_t sizeOfAnElement,
                    int(_USERENTRY *cmpFunc)(const void*, const void*));
field指向被排序集合的第一個元素,nElements是集合中元素的個數,sizeOfAnElement是一個元素所佔字節個數,cmpFunc是比較函數的函數指針。比較函數有兩個void指針類型參數並返回int型。一個callback執行完成就像一個通常的函數執行完成一樣。只是使用的是函數指針名而非函數名。
void qsort( ... , int(_USERENTRY *cmpFunc)(const void*, const void*))
   {
      /* sort algorithm  - note: item1 and item2 are void-pointers */
      int bigger=cmpFunc(item1, item2);  // make callback
      /* use the result */
   }

3. qsort使用的示例代碼
//-----------------------------------------------------------------------------------------
   // 3.3 How to make a callback in C by the means of the sort function qsort

   #include <stdlib.h>        // due to:  qsort
   #include <time.h>          //          randomize
   #include <stdio.h>         //          printf

   // comparison-function for the sort-algorithm
   // two items are taken by void-pointer, converted and compared
   int CmpFunc(const void* _a, const void* _b)
   {
      // you've got to explicitly cast to the correct type
      const float* a = (const float*) _a;
      const float* b = (const float*) _b;

      if(*a > *b) return 1;              // first item is bigger than the second one -> return 1
      else
         if(*a == *b) return  0;         // equality -> return 0
         else         return -1;         // second item is bigger than the first one -> return -1
   }


   // example for the use of qsort()
   void QSortExample()
   {
      float field[100];

      ::randomize();                     // initialize random-number-generator
      for(int c=0;c<100;c++)             // randomize all elements of the field
         field[c]=random(99);

      // sort using qsort()
      qsort((void*) field, /*number of items*/ 100, /*size of an item*/ sizeof(field[0]),
            /*comparison-function*/ CmpFunc);

      // display first ten elements of the sorted field
      printf("The first ten elements of the sorted field are .../n");
      for(int c=0;c<10;c++)
         printf("element #%d contains %.0f/n", c+1, field[c]);
      printf("/n");
   }

4. 如何對靜態C++成員函數實現callback
    這和對C函數實現callback一樣。靜態成員函數不需要一個對象來調用因此與帶有相同調用約定的C函數具有相同的簽名,調用參數和返回類型。
   
5. 如何對非靜態C++成員函數執行callback
(1)包裝方法
    指向非靜態成員函數的指針不同於通常的C函數指針,因爲它們需要被傳遞給類對象的this指針。因此通常的函數指針和非靜態成員函數具有不同和不兼容的簽名!如果想callback一個具體類的成員,只需要改變代碼從一個通常的函數指針爲指向成員函數的指針。但如果想callback一個任意類的非靜態成員函數呢?這個有點困難。我們需要寫一個靜態成員函數作爲包裝器。一個靜態成員函數與C函數有着相同的簽名!然後轉換想要調用的成員函數所在對象的指針類型爲void*,然後把它作爲一個額外參數或者通過全局變量傳遞給包裝器。如果使用的是全局變量,很重要的是確信它會總指向正確的對象!當然,也可以爲成員函數傳遞調用參數。包裝器將void指針轉換爲指向對應類實例的指針,然後調用成員函數。

(2)Example A: 將類實例的指針作爲額外參數傳遞
    函數DoltA處理類TClassA的對象。因此類TClassA的指針和靜態包裝函數TClassA::Wrapper_To_Call_Display的指針被傳遞給DoltA。這個包裝器就是callback函數。我們可以寫任意其他像TClassA一樣的類,然後用DoltA使用他們,只要這些其他的類提供必要的函數。注意:如果想自己設計callback接口時,這個方案很有用,它會優於另外一種方案。
   //-----------------------------------------------------------------------------------------
   // 3.5 Example A: Callback to member function using an additional argument
   // Task: The function 'DoItA' makes something which implies a callback to
   //       the member function 'Display'. Therefore the wrapper-function
   //       'Wrapper_To_Call_Display is used.

   #include <iostream.h>   // due to:   cout

   class TClassA
   {
   public:

      void Display(const char* text) { cout << text << endl; };
      static void Wrapper_To_Call_Display(void* pt2Object, char* text);

      /* more of TClassA */
   };


   // static wrapper-function to be able to callback the member function Display()
   void TClassA::Wrapper_To_Call_Display(void* pt2Object, char* string)
   {
       // explicitly cast to a pointer to TClassA
       TClassA* mySelf = (TClassA*) pt2Object;

       // call member
       mySelf->Display(string);
   }


   // function does something which implies a callback
   // note: of course this function can also be a member function
   void DoItA(void* pt2Object, void (*pt2Function)(void* pt2Object, char* text))
   {
      /* do something */

      pt2Function(pt2Object, "hi, i'm calling back using a argument ;-)");  // make callback
   }


   // execute example code
   void Callback_Using_Argument()
   {
      // 1. instantiate object of TClassA
      TClassA objA;

      // 2. call 'DoItA' for <objA>
      DoItA((void*) &objA, TClassA::Wrapper_To_Call_Display);
   }

(3)Example B: 類實例的指針存儲在全局變量中
    函數DoltB處理類TClassB的對象。靜態函數TClassB::Wrapper_To_Call_Display的指針被傳遞給了DoltB。這個包裝器是一個callback函數。包裝器使用全局變量void* pt2Object,然後顯式的將它轉換爲TClassB的實例。重要的是總是要初始全局變量指向正確的類實例。你可以寫任意的像TClassB一樣的類,然後用DoltB使用它們,只要這些其它類提供必要的函數。注意:如果有一個現存的callback接口並且不能改變,使用這種方案較好。但是這並非是一個好的方案應爲全局變量很危險且可能導致嚴重錯誤。
//-----------------------------------------------------------------------------------------
   // 3.5 Example B: Callback to member function using a global variable
   // Task: The function 'DoItB' makes something which implies a callback to
   //       the member function 'Display'. Therefore the wrapper-function
   //       'Wrapper_To_Call_Display is used.

   #include <iostream.h>   // due to:   cout

   void* pt2Object;        // global variable which points to an arbitrary object

   class TClassB
   {
   public:

      void Display(const char* text) { cout << text << endl; };
      static void Wrapper_To_Call_Display(char* text);

      /* more of TClassB */
   };


   // static wrapper-function to be able to callback the member function Display()
   void TClassB::Wrapper_To_Call_Display(char* string)
   {
       // explicitly cast global variable <pt2Object> to a pointer to TClassB
       // warning: <pt2Object> MUST point to an appropriate object!
       TClassB* mySelf = (TClassB*) pt2Object;

       // call member
       mySelf->Display(string);
   }


   // function does something which implies a callback
   // note: of course this function can also be a member function
   void DoItB(void (*pt2Function)(char* text))
   {
      /* do something */

      pt2Function("hi, i'm calling back using a global ;-)");   // make callback
   }


   // execute example code
   void Callback_Using_Global()
   {
      // 1. instantiate object of TClassB
      TClassB objB;


      // 2. assign global variable which is used in the static wrapper function
      // important: never forget to do this!!
      pt2Object = (void*) &objB;


      // 3. call 'DoItB' for <objB>
      DoItB(TClassB::Wrapper_To_Call_Display);
   }

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章