通用型函數指針

看了 kevinlynx 的一篇文章,而後按本身的理解從新實現一個通用型函數指針。javascript

前言

看了 kevinlynx 的一篇通用型函數指針的文章,發現使用到的技術知識本身都知道,因而想着本身也實現一個來練練手。java

背景

什麼是通用型的函數指針呢?linux

這個很差解釋,不過能夠用例子來讓你們看明白。c++

正常類型指針

對於日常的指針,好比整形指針,咱們直接能夠像 下面的形式操做 。git

void normal() {
 int one = 1;  int* pOne;  pOne = &one;  printf("pOne %d\n", *pOne);  int two = 2;  int* pTwo= &two;  printf("pTwo %d\n", *pTwo);  int three = 3;  int* pThree(&three);  printf("pThree %d\n", *pThree);  printf("end normal\n\n"); } 

這裏咱們能夠看到整形指針有這麼幾個性質。github

  1. 普通指針能夠在定義時初始化
  2. 普通指針能夠在正常賦值
  3. 咱們能夠操做指針的值

正常函數指針

那 函數指針 是什麼樣子呢?函數

void testPointFun(int num) {  printf("testPointFun %d\n",num); } void testPointFunTwo(int num, int num2) {  printf("testPointFunTwo %d %d\n",num, num2); } void pointFun() {  void (*pFunOne)(int);  pFunOne = testPointFun;  pFunOne(1);  void (*pFunTwo)(int) = testPointFun;  pFunTwo(2);  void (*pFunThree)(int)(testPointFun);  pFunThree(3);  typedef void (*PestPointFun)(int);  PestPointFun pFunFour = testPointFun;  pFunFour(4);  typedef void (*PestPointFunTwo)(int, int);  PestPointFunTwo pFunFive = testPointFunTwo;  pFunFive(5,5);  printf("end pointFun\n\n"); } 

咱們發現,普通指針也均可以作這些操做,可是咱們須要使用函數指針那麼很長很長的定義,即便使用 typedef , 也要爲每一種函數聲明單獨定義新類型的名字。學習

指望的函數指針

因而咱們想,能不能直接定義函數指針呢?spa

好比這樣指針

void wantPointFun() {  PointFun pointFunOne = testPointFun;  pointFunOne(6);  PointFun pointFunTwo = testPointFunTwo;  pointFunTwo(7,7);  printf("end wantPointFun\n\n"); } 

固然,根據一個函數名自動推導出對應的函數指針的技術能夠實現,可是cpp標準中又沒有這樣的技術我就不知道了。

咱們就假設cpp中如今沒有這樣的技術吧。

正文

既然目前標準中不支持這種技術,那咱們該如何實現呢?

初級通用函數指針

因而只好本身指定好類型了。

例如 這樣

template <typename _R, typename _P1> class functor { public:  typedef _R (*func_type)( _P1 ); public:  explicit functor( const func_type &func ) :   _func( func ) {  }  _R operator() ( _P1 p ) {   return _func( p );  } private:  func_type _func; }; int testPointFun(int num) {  printf("testPointFun %d\n",num);  return 0; } void firstPointFun() {  functor<int, int> cmd( testPointFun );  cmd( 1 ); } 

因而咱們經過重載類的運算符 () 來模擬函數調用就完美的解決問題了。

增強版通用函數指針

可是咱們既然可使用類來模擬函數(姑且稱爲函數對象吧), 那傳過來的函數指針會不會就是咱們的那個函數對象呢?

struct Func {  int operator() ( int i ) {   return i;  } }; void secondPointFun() {  functor<int, int> cmd1( testPointFun );  cmd1(1);  Func obj;  functor<int, int> cmd2(obj);  cmd2( 2 ); } 

咱們發現對於函數對象, 編譯不經過。提示這個錯誤

error: no matching function for call to 'functor<int, int>::functor(Func&)' 

報這個錯誤也正常,咱們的通用函數指針式 int (*)(int) 類型, 可是咱們傳進去的是 Func 類型,固然不匹配了。

這個時候咱們就會意識到須要對這個函數的類型進行抽象了,好比 這樣 。

template <typename _R, typename _P1,typename _FuncType> class functor { public:  typedef _FuncType func_type; public:  explicit functor( const func_type &func ) :   _func( func ) {  }  _R operator() ( _P1 p ) {   return _func( p );  } private:  func_type _func; }; int testPointFun(int num) {  printf("testPointFun %d\n",num);  return 0; } struct Func {  int operator() ( int num ) {   printf("Func class %d\n",num);   return num;  } }; void threePointFun() {  functor<int, int, int (*)(int)> cmd1( testPointFun );  cmd1(1);  Func obj;  functor<int, int, Func> cmd2(obj);  cmd2( 2 ); } 

這個時候咱們終於編譯經過了。

回頭思考人生

可是,編譯經過的代價倒是咱們手動指定函數指針的類型, 這與直接聲明函數指針變量有什麼區別呢?

好比對於上面的,咱們直接使用函數指針不是更方便嗎?

void fourPointFun() {  int (*cmd1)(int) ( testPointFun );  cmd1(1);  Func obj;  Func cmd2(obj);  cmd2( 2 ); } 

那咱們爲了什麼那樣這樣的尋找所謂的'通用型函數指針'呢?

答案是爲了統一函數指針的定義,對,是統一。

自動推導類型

那咱們能不能省去函數指針的類型呢?

貌似使用多態能夠省去函數指針的類型,可讓系統本身推導,而後咱們只須要調用函數便可。

例如 這樣

template <typename _R, typename _P1> struct handler_base {  virtual _R operator() ( _P1 ) = 0; }; template <typename _R, typename _P1, typename _FuncType> class handler : public handler_base<_R, _P1> { public:  typedef _FuncType func_type; public:  handler( const func_type &func ) :   _func( func ) {  }  _R operator() ( _P1 p ) {   return _func( p );  } public:  func_type _func; }; template <typename _R, typename _P1> class functor { public:  typedef handler_base<_R, _P1> handler_type ; public:  template <typename _FuncType>  functor( _FuncType func ) :   _handler( new handler<_R, _P1, _FuncType>( func ) ) {  }  ~functor() {   delete _handler;  }  _R operator() ( _P1 p ) {   return (*_handler)( p );  } private:  handler_type *_handler; }; int testPointFun(int num) {  printf("testPointFun %d\n",num);  return 0; } struct Func {  int operator() ( int num ) {   printf("Func class %d\n",num);   return num;  } }; void fivePointFun() {  functor<int, int>cmd1( testPointFun );  cmd1(1);  Func obj;  functor<int, int>cmd2(obj);  cmd2( 2 ); } 

支持任意參數

咱們經過模板和多態實現了指定參數的通用型函數指針。

因爲模板是編譯的時候肯定類型的,因此參數的個數須要編譯的時候肯定。

又因爲模板不支持任意類型參數,因此咱們只好把不一樣個數參數的模板都定義了。

這裏有涉及到怎麼優雅的定義不一樣個數參數的模板了。

去年我去聽過一個培訓,講的是就是c++的模板,重點講了偏特化。

咱們利用偏特化就能夠暫時解決這個問題。

實現代碼能夠參考個人 github 。

看了實現代碼,發現使用起來仍是很不方便。

functor<int, TYPE_LIST1(int)>cmd1( testPointFun ); cmd1(1); Func obj; functor<int, TYPE_LIST1(int)>cmd2(obj); cmd2( 2 ); functor<int, TYPE_LIST2(int,int)>cmd3( testPointFunTwo ); cmd3(1,2); 

須要咱們手動指定參數的個數,以及傳進去參數的類型。

因爲咱們不能自動推導參數的類型,因此類型必須手動指定,可是個數咱們應該能夠在編譯器期肯定吧。

得到宏的個數

如今咱們的目的是這樣的使用函數指針。

functor<int, TYPE_LIST(int)>cmd1( testPointFun ); cmd1(1); Func obj; functor<int, TYPE_LIST(int)>cmd2(obj); cmd2( 2 ); functor<int, TYPE_LIST(int,int)>cmd3( testPointFunTwo ); cmd3(1,2); 

這個卻是很容易實現。好比 這樣

#define NUM_PARAMS(...) NUM_PARAMS_OUTER(__VA_ARGS__, NUM_PARAMS_EXTEND()) #define NUM_PARAMS_OUTER(...) NUM_PARAMS_INTER(__VA_ARGS__) #define NUM_PARAMS_INTER( _1, _2, _3, _4, _5, _6, _7, _8, _9,_10, _11,_12,_13,_14,_15,_16, N, ...) N #define NUM_PARAMS_EXTEND() 16,15,14,13,12,11,10, 9,8,7,6,5,4,3,2,1,0 #define TYPE_LIST1( T1 ) type_list<T1, null_type> #define TYPE_LIST2( T1, T2 ) type_list<T1, TYPE_LIST1( T2 )> #define TYPE_LIST3( T1, T2, T3 ) type_list<T1, TYPE_LIST2( T2, T3 )> #define TYPE_LIST(...) TYPE_LIST_N(NUM_PARAMS(__VA_ARGS__), __VA_ARGS__) #define TYPE_LIST_N(n,...) TYPE_LIST_N_FIX(n, __VA_ARGS__) #define TYPE_LIST_N_FIX(n, ...) TYPE_LIST##n(__VA_ARGS__) 

這個實現仍是有一點不爽: 咱們須要寫出全部可能的 TYPE_LISTn.

能不能使用宏來作到這個呢?

宏中怎麼才能判斷出到到達最後一個參數或者沒有參數了呢?

仍是依靠獲得宏個數的技術。

可是通過嵌套嘗試,發現宏時不能遞歸展開的。

好吧,既然不能遞歸展開,那也只能到達這一步了。

源代碼

源代碼能夠參考個人 github .

參考資料

相關文章
相關標籤/搜索