引子
最近群裡比較熱鬧,大家都在山寨c++11的std::bind,三位童孩分別實現了自己的bind,代碼分別在這裡:
木頭雲的實現:連接稍後補上。
mr.li的實現:https://code.google.com/p/y-code-svn/source/browse/#svn%2Ftrunk%2Fc%2B%2B%2FBex%2Fsrc%2FBex%2Fbind
null的實現:http://www.cnblogs.com/xusd-null/p/3693817.html#2934538
這些實現思路和ms stl的std::bind的實現思路是差不多的,只是在實現的細節上有些不同。個人覺得木頭雲的實現更簡潔,本文中的簡單實現也是基於木頭雲的bind之上的,在此表示感謝。下面我們來分析一下bind的基本原理。
bind的基本原理
bind的思想實際上是一種延遲計算的思想,將可調用對象保存起來,然後在需要的時候再調用。而且這種綁定是非常靈活的,不論是普通函數、函數對象、還是成員函數都可以綁定,而且其參數可以支持占位符,比如你可以這樣綁定一個二元函數auto f = bind(&func, _1, _2);,調用的時候通過f(1,2)實現調用。關於bind的用法更多的介紹可以參考我博客中介紹:http://www.cnblogs.com/qicosmos/p/3302144.html。
要實現一個bind需要解決兩個問題,第一個是保存可調用對象及其形參,第二個是如何實現調用。下面來分析如何解決這兩個問題。
保存可調用對象
實現bind的首先要解決的問題是如何將可調用對象保存起來,以便在後面調用。要保存可調用對象,需要保存兩個東西,一個是可調用對象的實例,另一個是可調用對象的形參。保存可調用對象的實例相很簡單,因為bind時直接要傳這個可調用對象的,將其作為一個成員變量即可。而保存可調用對象的形參就麻煩一點,因為這個形參是變參,不能直接將變參作為成員變量。如果要保存變參的話,我們需要用tuple來將變參保存起來。
可調用對象的執行
bind的形參因為是變參,可以是0個,也可能是多個,大部分情況下是占位符,還有可能占位符和實參都有。正是由於bind綁定的靈活性,導致我們不得不在調用的時候需要找出哪些是占位符,哪些是實參。如果某個一參數是實參我們就不處理,如果是占位符,我們就要將這個占位符替換為對應的實參。比如我們綁定了一個三元函數:auto f = bind(&func, _1, 2, _2);調用時f(1,3);由於綁定時有三個參數,一個實參,兩個占位符,調用時傳入了兩個實參,這時我們就要將占位符_1替換為實參1,占位符_2替換為實參3。這個占位符的替換需要按照調用實參的順序來替換,如果調用時的實參個數比占位符要多,則忽略多余的實參。
調用的實參,我們也會先將其轉換為tuple,用於在後面去替換占位符時,選取合適的實參。
bind實現的關鍵技術
將tuple展開為變參
前面講到綁定可調用對象時,將可調用對象的形參(可能含占位符)保存起來,保存到tuple中了。到了調用階段,我們就要反過來將tuple展開為可變參數,因為這個可變參數才是可調用對象的形參,否則就無法實現調用了。這裡我們會借助於一個整形序列來將tuple變為可變參數,在展開tuple的過程中我們還需要根據占位符來選擇合適實參,即占位符要替換為調用實參。
根據占位符來選擇合適的實參
這個地方比較關鍵,因為tuple中可能含有占位符,我們展開tuple時,如果發現某個元素類型為占位符,則從調用的實參生成的tuple中取出一個實參,用來作為變參的一個參數;當某個類型不為占位符時,則直接從綁定時生成的形參tuple中取出參數,用來作為變參的一個參數。最終tuple被展開為一個變參列表,這時,這個列表中沒有占位符了,全是實參,就可以實現調用了。這裡還有一個細節要注意,替換占位符的時候,如何從tuple中選擇合適的參數呢,因為替換的時候要根據順序來選擇。這裡是通過占位符的模板參數I來選擇,因為占位符place_holder<I>的實例_1實際上place_holder<1>, 占位符實例_2實際上是palce_holder<2>,我們是可以根據占位符的模板參數來獲取其順序的。
bind的簡單實現
#include <tuple> #include <type_traits> using namespace std; template<int...> struct IndexTuple{}; template<int N, int... Indexes> struct MakeIndexes : MakeIndexes<N - 1, N - 1, Indexes...>{}; template<int... indexes> struct MakeIndexes<0, indexes...> { typedef IndexTuple<indexes...> type; }; template <int I> struct Placeholder { }; Placeholder<1> _1; Placeholder<2> _2; Placeholder<3> _3; Placeholder<4> _4; Placeholder<5> _5; Placeholder<6> _6; Placeholder<7> _7; Placeholder<8> _8; Placeholder<9> _9; Placeholder<10> _10; // result type traits template <typename F> struct result_traits : result_traits<decltype(&F::operator())> {}; template <typename T> struct result_traits<T*> : result_traits<T> {}; /* check function */ template <typename R, typename... P> struct result_traits<R(*)(P...)> { typedef R type; }; /* check member function */ template <typename R, typename C, typename... P> struct result_traits<R(C::*)(P...)> { typedef R type; }; template <typename T, class Tuple> inline auto select(T&& val, Tuple&)->T&& { return std::forward<T>(val); } template <int I, class Tuple> inline auto select(Placeholder<I>&, Tuple& tp) -> decltype(std::get<I - 1>(tp)) { return std::get<I - 1>(tp); } // The invoker for call a callable //
測試代碼:
void TestFun1(int a, int b, int c) { } void TestBind1() { Bind(&TestFun1, _1, _2, _3)(1, 2, 3); Bind(&TestFun1, 4, 5, _1)(6); Bind(&TestFun1, _1, 4, 5)(3); Bind(&TestFun1, 3, _1, 5)(4); }bind更多的實現細節
由於只是展示bind實現的關鍵技術,很多的實現細節並沒有處理,比如參數是否是引用、右值、const volotile、綁定非靜態的成員變量都還沒處理,僅僅供學習之用,並非是重復發明輪子,只是展示bind是如何實現, 實際項目中還是使用c++11的std::bind為好。null同學還圖文並茂的介紹了bind的過程:http://www.cnblogs.com/xusd-null/p/3698969.html,有興趣的童孩可以看看.
關於bind的使用
在實際使用過程中,我更喜歡使用lambda表達式,因為lambda表達式使用起來更簡單直觀,lambda表達式在絕大多數情況下可以替代bind。