boost::function和boost:bind是一對強大的利器。相信用過的童鞋多少有些體會。
雖然平時在用boost::function,但是用的時候心中總會一些不安,因為不知道它是怎麼實現的。於是,就自己琢磨著簡單的實現一下,搞明白基本的原理。
對於這個簡單實現,有以下幾個目標:
1、選取比較常見的接收2個參數的情況。
2、支持普通函數/函數指針、成員函數指針。
3、兼容函數對象、函數適配器/boost::bind。
首先,定義一個基類:
1 template<typename R, typename T1, typename T2> 2 class base 3 { 4 public: 5 virtual ~base() 6 { 7 } 8 9 virtual R operator()(T1, T2) = 0; 10 };
然後再實現一個普通函數/函數指針的版本:
1 template<typename R, typename T1, typename T2> 2 class func : public base<R, T1, T2> 3 { 4 public: 5 func(R (*ptr)(T1, T2)) 6 : ptr_(ptr) 7 { 8 } 9 10 virtual R operator()(T1 a, T2 b) 11 { 12 return ptr_(a, b); 13 } 14 15 private: 16 R (*ptr_)(T1, T2); 17 };
接著,實現支持成員函數指針的版本:
1 template<typename R, typename Class, typename T> 2 class member : public base<R, Class, T> 3 { 4 }; 5 6 template<typename R, typename Class, typename T> 7 class member<R, Class*, T> : public base<R, Class*, T> 8 { 9 public: 10 member(R (Class::*ptr)(T)) 11 : ptr_(ptr) 12 { 13 } 14 15 virtual R operator()(Class* obj, T a) 16 { 17 return (obj->*ptr_)(a); 18 } 19 20 private: 21 R (Class::*ptr_)(T); 22 };
可能有的童鞋要問,為什麼這裡要有一個空的member類呢?這個問題放到下面解釋。
自然的輪到最後一個種情況,函數對象/boost::bind類型的了:
1 template<typename T, typename R, typename T1, typename T2> 2 class functor : public base<R, T1, T2> 3 { 4 public: 5 functor(const T& obj) 6 : obj_(obj) 7 { 8 } 9 10 virtual R operator()(T1 a, T2 b) 11 { 12 return obj_(a, b); 13 } 14 15 private: 16 T obj_; 17 };
最後,就是可用的function類了,實現如下:
1 template<typename T> 2 class function 3 { 4 }; 5 6 template<typename R, typename T1, typename T2> 7 class function<R (T1, T2)> 8 { 9 public: 10 template<typename Class, typename _R, typename _T2> 11 function(_R (Class::*ptr)(_T2)) 12 : ptr_(new member<R, T1, T2>(ptr)) 13 { 14 } 15 16 template<typename _R, typename _T1, typename _T2> 17 function(_R (*ptr)(_T1, _T2)) 18 : ptr_(new func<R, T1, T2>(ptr)) 19 { 20 } 21 22 template<typename T> 23 function(const T& obj) 24 : ptr_(new functor<T, R, T1, T2>(obj)) 25 { 26 } 27
28 29 ~function() 30 { 31 delete ptr_; 32 } 33 34 virtual R operator()(T1 a, T2 b) 35 { 36 return ptr_->operator()(a, b); 37 } 38 39 private: 40 base<R, T1, T2>* ptr_; 41 };
大家可能注意到了,和前面的member類一樣,function也有一個空的類,那麼這些有什麼用呢?
這麼做的原因,主要是利用模板偏特化來進行類型萃取,正常的function聲明的時候,比如function<int (int, int)>而不是func<int, int, int>。所以用模板的偏特化的版本
template<typename R, typename T1, typename T2> class function<R (T1, T2)>
就可以把int (int, int)萃取為R = int,T1 = int,T2 = int了。
同理,對於member類,由於一般我們將成員函數指針綁定到function的時候,比如int function(Type*, int),其中Type是成員函數所屬類。也就是說在function中的成員ptr_的類型是base<int, Type*, int>,那麼在function的構造函數中構造的member類的類型就是member<int, Type*, int>,也就是Class = Type*,但是我們需要的卻是Class = Type。所以這裡得用偏特化萃取一下:
template<typename R, typename Class, typename T> class member<R, Class*, T> : public base<R, Class*, T>
這樣得到的Class模板形參就會被編譯器決議為Type,而不是Type*了。
另外提一下,在function的3種情況的構造函數是模板成員函數,而不是普通成員函數:
template<typename Class, typename _R, typename _T2> function(_R (Class::*ptr)(_T2)) : ptr_(new member<R, T1, T2>(ptr)) { } template<typename _R, typename _T1, typename _T2> function(_R (*ptr)(_T1, _T2)) : ptr_(new func<R, T1, T2>(ptr)) { } template<typename T> function(const T& obj) : ptr_(new functor<T, R, T1, T2>(obj)) { }
前2種情況,普通函數/函數指針對應的構造函數和成員函數指針對應的構造函數實現為成員模板,主要是為了兼容參數的隱式轉換,例如聲明一個function的類型為function<int (int, int)> foo,調用的時候卻傳入兩個double類型,foo(1.1, 2.2), double類型隱式轉換成了int類型。這樣也符合boost:function本來的兼容可轉換的調用物這一特性。
而第3種情況的成員模板,是為了獲取傳入的函數對象/boost::bind的類型,以便在存儲在functor的數據成員中,這也是為什麼functor類的模板參數比其他版本多了一個的原因。
然後,我們來測試一下:
1 int get(int a, int b) 2 { 3 std::cout << a+b << std::endl; 4 return 0; 5 } 6 7 class Point 8 { 9 public: 10 int get(int a) 11 { 12 std::cout << "Point::get called: a = "<< a << std::endl; 13 return a; 14 } 15 int doit(int a, int b) 16 { 17 std::cout << "Point::doit called: a = "<< a+b << std::endl; 18 return a+b; 19 } 20 }; 21 22 int main(int argc, char const *argv[]) 23 { 24 function<int (int, int)> foo(get); 25 foo(10.1, 10.3); 26 27 function<int (Point*, int)> bar(&Point::get); 28 Point point; 29 bar(&point, 30); 30 31 function<int (int, int)> obj(boost::bind(&Point::doit, &point, _1, _2)); 32 obj(90, 100); 33 }
結果為:
20 Point::get called: a = 30 Point::doit called: a = 190
可以看到,輸出的內容正是所期望的結果。
(完)