作者:Kevin Lynx 需求: 開發(fā)一種組件,用以包裝C函數(shù)、通常的函數(shù)對象、成員函數(shù),使其對外保持一種一致的接口。我將最終的 組件稱為functor,這里的functor與loki中的functor以及boost中的function功能一致,同STL中的functor 在概念層次上可以說也是一樣的。那么,functor其實也可以進一步傳進其他functor構成新的functor。 C++世界里還有一種組件,稱做bind(er),例如STL中的binder1st、binder2nd,以及boost中的bind。所謂 的bind是將一些參數(shù)與函數(shù)之類的關聯(lián)起來,當執(zhí)行該bind創(chuàng)建的對象時,庫會自動將之前bind的參數(shù)傳 遞給bind創(chuàng)建的對象。bind創(chuàng)建出來的對象在某種程度上來說也是一種functor。 實現(xiàn): 包裝C函數(shù)和函數(shù)對象的functor事實上是一致的,而實現(xiàn)包裝成員函數(shù)的functor則需要多傳入一個對象參數(shù)。 因此這里先討論包裝C函數(shù)和函數(shù)對象的functor。 包裝C函數(shù): 思考下各種不同的C函數(shù)的共同點和不同點,共同點就是這些函數(shù)都有一個返回值,參數(shù)個數(shù)可能相同,可能 不同,參數(shù)類型可能相同可能不同??紤]到模板對于類型的泛化特性,對于參數(shù)類型來說,可以輕松實現(xiàn)無 關性。而至于參數(shù)個數(shù)的泛化,則要復雜點。這里先考慮實現(xiàn)參數(shù)個數(shù)為1個的functor:
要使用這個類模板,可以這樣:
 functor< int, int> cmd( func ); // int func( int )
 cmd( 1 );  這樣,functor這個類模板就可以保存所以只有一個參數(shù)返回值任意的函數(shù)。但是這里首要的問題是,這個 類模板無法保存具有相同類型的函數(shù)對象,例如函數(shù)對象:
Func obj; 因為obj的類型事實上是Func,并不是一般的函數(shù)類型(例如 int (*)(int) )。那么,這里就需要 將functor::func_type這個typedef泛化。
包裝函數(shù)對象: 要實現(xiàn)這個目的,其實并不那么容易。一種比較直接的方法是我們把functor::func_type通過模板參數(shù)顯示地讓用戶配置, 例如:
那么,現(xiàn)在就可以這樣使用functor:
 functor< int, int, int(*)( int)> cmd( func );  cmd( 1 ); // 測試函數(shù)對象
 Func obj;  functor< int, int, Func> cmd2( obj );  cmd2( 2 );  自動推導類型:
但是,這種顯示指定functor保存的函數(shù)(函數(shù)對象)的類型顯然是不方便的。我希望functor可以自動獲取我們要 保存的東西(C函數(shù),函數(shù)對象,為方便起見,以下全部簡稱為函數(shù))的類型。而一個函數(shù)模板正可以做到這一點。 以下簡寫很多思考過程,直接給出一個解決方案:
代碼多了一倍,還增加了多態(tài)機制,使用了動態(tài)內(nèi)存分配(這總會為我們增加麻煩),所以這些,就是為了提供 給用戶一個方便一致的接口。現(xiàn)在我們可以這樣使用functor:
 functor< int, int> cmd1( func );  cmd1( 1 );   Func obj;  functor< int, int> cmd2( obj );  cmd2( 2 );   雖然目標實現(xiàn)了,可是看上去并不完美。礙眼的就是那個virtual,以及new/delete。不過因為這里離我的最終 目標還很遠,所以姑且不管這些。接下來要實現(xiàn)的是讓functor支持任意個參數(shù)(事實上任意個是不可能的)。
讓更多的類型加入進來: 這里支持任意個參數(shù)似乎不現(xiàn)實,因為C++并不支持這樣的語法形式:
 template <typename _R,  > class functor;  也就是說模板并不支持可變參數(shù)。(可變參數(shù)那是C里面的東西,C++本身就不鼓勵)
這里,最簡單的實現(xiàn)方法就是定義各種functor,支持0個參數(shù)的functor,支持一個參數(shù)的functor(我們以上實現(xiàn)的), 支持兩個參數(shù)的functor,等等。相應的,我們給每一個functor命名為functor0,functor1,functor2,。。。 這確實是一種樸實的解決方法,但同時看上去也確實很不優(yōu)雅。我們其實完全可以通過一種模板技術讓functor1這種 丑陋的命名方式消失,這就是模板偏特化(partial specialization)。 Loki中的魔法: 首先我們要讓functor這個頂層類可以看上去似乎支持可變長度的模板參數(shù)。這個可以通過loki的TypeList實現(xiàn)。但是 我們這里并不會用到特別復雜的TypeList技術。所謂TypeList,大致上核心在于以下類型:
然后我們可以以一種遞歸的方式去容納任意長度的類型列表(所謂type list): type_list<int, type_list<char, float> > 在實際實現(xiàn)時,我們通常會為每一個type list添加一個在loki中叫null_type的類型,就像C字符串末尾的'\0'一樣: type_list<int, type_list<char, null_type> > 而null_type很簡單,就是一個沒有任何東西的空類型:
struct null_type { };  為了更方便地產(chǎn)生type_list,我們按照loki中的做法,定義一系列的宏:
注:以上內(nèi)容基本和<C++設計新思維>部分內(nèi)容相同
講述了以上基本內(nèi)容(我希望你能理解),接下來我要闡述下我的目的。我會把新的functor定義成:
 template <typename _R, typename _ParamList> class functor;  如你所見,這和之前的functor本質(zhì)上是一樣的,我只不過改變了一個模板參數(shù)的名字(_ParamList)?,F(xiàn)在當我們使用 functor的時候,會這樣:
 functor< void, void>  functor< int, TYPE_LIST1( char )>  functor< void, TYPE_LIST2( char, float )>  我們回頭看下之前創(chuàng)建的functor模塊的三個類是如何相互關聯(lián)的:functor提供給外部用戶接口,handler保存函數(shù)、回調(diào) 函數(shù),handler_base則主要是提供給functor一個可以保存的類型(所以functor里保存的是functor_base)以及聲明各種接口。 為什么需要提供handler_base,而不直接保存handler?因為handler需要保存函數(shù)的類型_FuncType,而這個類型只能在functor構造 函數(shù)里被提取出來。局限于這個原因,我加入了handler_base,并不得不加入了virtual,而為了滿足virtual的需要,我進一步 不得不將handler方在堆棧上。
現(xiàn)在,我要實現(xiàn)通過functor不同的模板參數(shù)(主要在于_ParamList),產(chǎn)生不同的handler_base。關鍵在于我要產(chǎn)生各種不同的 handler_base!現(xiàn)在我省略很多思考過程,直接給出一種架構:  template <typename _R, typename _ParamList> struct handler_base;   template <typename _R> struct handler_base<_R, void> : public handler_type_base<_R> {
virtual _R operator() ( void ) = 0;
};   template <typename _R, typename _P1> struct handler_base<_R, TYPE_LIST1( _P1 )> : public handler_type_base<_R> {
typedef _P1 param1_type;

virtual _R operator() ( _P1 ) = 0;
};  /// TODO:添加更多類型的偏特化版本  template <typename _R, typename _ParamList, typename _FuncType> class handler : public handler_base<_R, _ParamList> {
public:
typedef _FuncType func_type;

typedef handler_base<_R, _ParamList> base_type;
typedef typename base_type::param1_type param1_type;
/// TODO:更多的類型定義
public:
handler( const func_type &func ) :
_func( func )
{
}

_R operator() ()
{
return _func();
}

_R operator() ( param1_type p )
{
return _func( p );
}
/// 省略部分代碼
/// functor
template <typename _R, typename _ParamList>
class functor
{
public:
typedef handler_base<_R, _ParamList> handler_type ;

typedef typename handler_type::param1_type param1_type;
typedef typename handler_type::param2_type param2_type;
typedef typename handler_type::param3_type param3_type;
/// TODO:更多類型
public:
template <typename _FuncType>
functor( _FuncType func ) :
_handler( new handler<_R, _ParamList, _FuncType>( func ) )
{
}
~functor()
{
delete _handler;
}

_R operator() ()
{
return (*_handler)();
}

_R operator() ( param1_type p )
{
return (*_handler)( p );
}
/// 省略部分代碼

 現(xiàn)在,各種偏特化版本的handler_base,其實就相當于實現(xiàn)了各種參數(shù)個數(shù)的functor,也就是functor0,functor1等。但是 現(xiàn)在有個很直接的問題,例如當functor<void, int>定義了一個參數(shù)時,functor::handler_type里就沒有param2_type之類的 類型定義,使用的偏特化版本handler_base也沒有部分param之類的類型定義。這會引起編譯出錯。為了解決這個辦法,我不得 不再引入一個用于類型定義的基類:
然后各種偏特化handler_base版本從handler_type_base繼承:
解決了這個編譯錯誤問題,整個functor就基本實現(xiàn)了?,F(xiàn)在可以這樣使用functor: 沒有參數(shù)的函數(shù):
 functor< void, void> cmd4( func3 );  cmd4();  兩個參數(shù)的函數(shù):
 functor< void, TYPE_LIST2( int, char)> cmd3( func2 );  cmd3( 3, 'a' );  我稍微提下編譯器大致的處理方法:當functor<void, void> cmd4( func3 )時,functor::handler_type為handler_base<void, void>偏特 化版本。該版本定義了void operator()()函數(shù)。當cmd4()時,就會調(diào)用到handler::operator()()函數(shù)。該函數(shù)回調(diào)func3函數(shù),完成調(diào)用。
完結,將成員函數(shù)包含進來: 關于包裝成員函數(shù),其實很簡單,只是在調(diào)用時需要一個該類的對象而已。這里直接從handler_base派生:
在functor中加入另一個構造函數(shù):
 template <typename _ObjType, typename _FuncType>  functor( _ObjType &obj, _FuncType func ) :  _handler( new mem_handler<_R, _ParamList, _FuncType, _ObjType>( obj, func ) ) {
} 一切都很完美。使用時:
 Test obj2; // Test是一個類
 functor< void, TYPE_LIST1( int)> cmd5( obj2, &Test::display );  cmd5( 1 ); 結束語: 雖然我們最終的目的實現(xiàn)了,但是這還是不夠完美。我們還要處理functor的拷貝行為,因為functor天生就是被用來 四處拷貝的。一旦涉及到拷貝,我們就不得不小心翼翼地處理好functor中的那個被new出來的對象。作為一個C++程序員, 你應該時刻警惕放在heap上的東西,建立對heap上的警覺感是很重要的。這里我不得不承認在后期實現(xiàn)中,我直接搬了 loki中的很多方案。如果你不想讓這個functor看上去那么優(yōu)雅,那你完全可以寫出functor0,functor1之類的東西。 參考資料: <C++ template>類模板的偏特化章節(jié) <Modern C++ design>type list, functor章節(jié) loki::functor源代碼 boost:;function源代碼 stl::bind1st源代碼 stl::ptr_fun相關源代碼
|