看了 kevinlynx 的一篇文章,然後按自己的理解重新實現一個通用型函數指針。
前言
看了 kevinlynx 的一篇通用型函數指針的文章,發現使用到的技術知識自己都知道,於是想着自己也實現一個來練練手。
背景
什麼是通用型的函數指針呢?
這個不好解釋,不過可以用例子來讓大家看明白。
正常類型指針
對於平常的指針,比如整形指針,我們直接可以像 下面的形式操作 。
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"); }
這裏我們可以看到整形指針有這麼幾個性質。
普通指針可以在定義時初始化
普通指針可以在正常賦值
我們可以操作指針的值
正常函數指針
那 函數指針 是什麼樣子呢?
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 , 也要爲每一種函數聲明單獨定義新類型的名字。
期望的函數指針
於是我們想,能不能直接定義函數指針呢?
比如這樣
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 .
參考資料
本文來自:Linux學習網