C11/C14 出現了許多漂亮的特性,好比說auto, shared_ptr等。看到這些特性,相信用過boost不少年的童鞋內心暗暗一笑:哥已經爽了XX年了 :)app
boost::bind就是boost庫中一個很好玩的東西。
它能將各類不一樣簽名的函數或者方法bind成你須要的簽名的形式。簡單的來講,就是一個bind1st, bind2nd的增強版。
我寫這個筆記的目的不在於介紹它的使用。而是爲了解開她的面紗,脫掉她的什麼什麼。所以我先假設你們對它的使用都很熟了。函數
考察下面一個有趣的例子:spa
void func(int){ } void use_func(boost::function<void (int, int) > f) { f(1,2); } void test_bind2() { use_func(boost::bind(&func, _1)); }
它能經過編譯嗎?有運行錯誤嗎?指針
帶着這個問題,來看看它的源碼。code
首先從這個問題開始,boost::bind是幹嗎的,要解決什麼問題呢?
它的本質就是預先把函數指針和參數(包括佔位符參數和實際參數)存儲起來,等到調用的時候再進行真正的函數調用。
說白了,它就是一個functor。在這句話裏涉及到幾個關鍵詞,函數指針,參數的存儲;調用。
很天然的,咱們能夠想象它必然是這個樣子的:blog
namespace boost { template<...> // 一大堆的模板,可能的樣子是 template< template R, class F, class Arg1, class Arg2...> class bind_t : public ... // 一些父類 { public: RETURN_TYPE operator() (Arg1 ag, ...) // 幾個參數 private: void* func_ptr; Args1 arg; Args2 ...; }; }
上面只是咱們的猜想,真正的boost::bind可能跟咱們想的不同。可是它必定要包含:
1) 函數指針存儲 ) --> 對應咱們的Functor ;
2) 參數存儲 (佔位符,和實際參數) --> 對應咱們的Args ...
3) 調用 --> operator()繼承
因爲boost::bind是大神寫的,確定代碼沒有上面的那麼醜。讓咱們一步一步來完善它。接口
咱們把參數用一個類型來存儲起來,讓代碼更乾淨一點。不妨把這個參數類命名爲:storage。
因爲boost::bind最多支持9個參數的狀況,所以對應的,咱們能夠定義九個類。圖片
template<class A1> struct storage1 { explicit storage1 (A1 a1) : a1_(a1) {} A1 a1_; }; template<class A1, class A2> struct storage2 : public storage1<A1> { storage2 (A1 a1, A2 a2) : stroage1<A1>(a1), a2_(a2) {} A2 a2_; }; ...// 省略storage3... storage9
storage之間用繼承能夠省掉不少代碼。並且會帶來其餘的方便。ci
可是,佔位符怎麼辦呢?咱們都知道在boost::bind裏,用_1, _2 ...等幾個佔位符來表示第幾個參數等待後續傳入。
這些佔位符(_1, _2 ...)究竟是什麼呢? 在boost/bind/placeholders.hpp裏面有這樣的定義。
boost::arg<1> _1; boost::arg<2> _2; //...
而boost::arg是一個簡單的模板類型:
template <int I> struct arg { arg() {} ... };
這樣,根據模板參數 的不一樣,就有了9個不一樣類型的變量。
如今,有了佔位符。本質上,它也是一個變量,對應着一個模板類型。
爲了區分佔位符和普通數據類型,須要對storage作特殊處理。因爲storage是一個模板類。所以,特殊處理的方案就是偏特化:
template <int I> struct storage1< boost::arg<I> > { ...// ??? }
這樣每一個storage都對應這一個偏特化版本。問題是省略的地方(...)應該填入什麼樣的代碼?
回到偏特化的目的上,偏特化的目的是要讓以後函數調用的時候,能準確的根據參數類型(是佔位符,仍是實參),選擇函數調用的參數。
既然這樣,咱們須要加入某種機制,使得兩種類型,普通類型和特化類型分別返回不一樣的結果。
C++ trait是一種選擇,好比咱們能夠在普通版本和偏特化版本中加入一個arg_type變量。分別指向不一樣的類型(T 和arg)。
但boost::bind卻不是這樣作的。 其成員變量A1 a_。就能夠做爲一種標誌位。
所以這個偏特化版本的完整定義是:
template <int I> struct storage1< boost::arg<I> > { explicit storage1( boost::arg<I> ) {} static boost::arg<I> a1_() { return boost::arg<I>(); } ...// 省略的部分代碼與文檔的邏輯無關 }
這樣咱們就能夠定義某種結構來取參數了。
好比在調用的時候,咱們能夠把傳入的參數構形成某種類型,而後在參數調用的過程當中,根據返回類型,決定使用a1_仍是傳入的參數。
注意到,若是該類型是個佔位符,boost::bind將a_ 從變量變成了靜態函數!達到了節省空間的目的。
如今咱們有了一個參數的storage,不妨把它命名爲storage1。而boost::bind支持九個參數,storage從一到九的過程是怎樣的呢?
繼承和組合都能達到咱們的目的。boost::bind採用的是繼承,storage2繼承storage1, storage3繼承storage2... 以此類推。以下圖示:
採用繼承的好處是不只能夠省下許多代碼,並且對空間的節省也有必定的好處。
boost::bind將構造boost::bind時,和調用時傳入參數"構造的類型"統一塊兒來。
在構造時爲存儲參數和佔位符構造一個storage;在取參數的時候須要傳入真正的參數用以取代佔位符,把這些參數再構造另外一個storage。
當調用觸發時,根據前一個storage(構造時)中參數的類型作不一樣處理:若是是佔位符,則從後者中取參數;不然,從前者中取。
這樣作,也是行得通的。可是boost::bind採起的方案是一種更優雅的辦法。
既然有爲佔位符而生的boost::arg, 爲何不另外建立一個爲普通類型而生的模板類呢?
這個類是value 類。
template<class T> class value { public: value(T const & t): t_(t) {} T & get() { return t_; } T const & get() const { return t_; } bool operator==(value const & rhs) const { return t_ == rhs.t_; } private: T t_; };
這樣storage模板的類型多是value, 也多是boost::arg。
取參數的時候就比較簡單了。
假設s_cont 表示構造boost::bind時建立的storage類,而s_call表示調用建立的類。爲這個storage加上operator[] 操做符,以storage 1爲例:
template <class A1> struct storage { A1 operator[] (boost::arg<1>) const { return a1_; } template<class T> T & operator[] (_bi::value<T> & v) const { return v.get(); } ... };
在參數調用的時候,咱們只須要
s_call[s_cont::a1]
就能拿到對應的類型。
因爲取參數的操做與存儲無關,能夠將這兩個職責分離開來。定義listN類,分別接口繼承自stroageN,並將operator[]操做符置於其中。
固然,listN不只僅只有這一個職責,它還有另外一個職責,這也是爲何開頭部分的代碼在調用的時候( f(1, 2) )雖然傳入的參數個數不匹配,卻能正確的調用的緣由。
如今咱們能夠改寫boost::bind,它應該有相似以下的定義:
template <class R, class F, class L> class bind { public: bind_t(F f, L const & l): f_(f), l_(l) {} ... private: F f_; L l_; };
從引言開始的例子能夠看出, boost::bind(&f, _1) 這個明明只有一個參數的Functor,能正確的轉換成兩個參數的。實際上,只要保證第一個參數(已有)類型匹配,它根本不關心後面跟了多少個參數。
所以,能夠猜想,boost::bind 至少實現了九種重載的operator。
template<class A1> result_type operator()(A1 & a1) const { list1<A1 &> a(a1); ...// } template<class A1, class A2> result_type operator()(A1 & a1, A2 & a2) { list2<A1 &, A2 &> a(a1, a2); ...// } ....
省略的部分是什麼樣的代碼?
如今,boost::bind 參數存儲的部分真正的類間關係圖以下:
從引言部分的例子能夠看到,儘管咱們傳入 f(10, 20) 以兩個參數的方式去調用真正的函數,卻沒有出錯。
這說明,boost::bind能根據函數真正須要參數的類型,而不是調用時傳入的類型去匹配正確的調用。
而咱們在使用boost::bind的時候,在構造bind的結構時,傳入了正確的參數類型!
包含這些正確參數類型的參數保存在內部結構listN 中。所以,只要在listN中實現對應的operator 就能保證正確的調用:
template<class A1, class A2> result_type operator()(A1 & a1, A2 & a2) { list2<A1 &, A2 &> a(a1, a2); BOOST_BIND_RETURN l_(type<result_type>(), f_, a, 0); }
把注意力放到最後一行代碼,能夠看到。構造boost::bind時候傳入的前期綁定的參數信息保存在l_ 中,函數指針保存在f_ 中。真正調用時候傳入的參數信息保存在a 中。
有了這些信息,咱們就能保證調用的正確性了。
template<class F, class A> void operator()(type<void>, F & f, A & a, int) { unwrapper<F>::unwrap(f, 0)(a[base_type::a1_], a[base_type::a2_]); } ...
固然,能夠看到,咱們使用boost::bind的時候,傳入的模板參數並非template< class R, class F, class L> 這三個。所以boost::bind須要定義一系列的接口(對應着九個不一樣類型的參數個數),並最終返回正確的bind_t類型。
下面是兩個參數的一個例子:
template<class R, class B1, class B2, class A1, class A2> _bi::bind_t<R, BOOST_BIND_ST R (BOOST_BIND_CC *) (B1, B2), typename _bi::list_av_2<A1, A2>::type> BOOST_BIND(BOOST_BIND_ST R (BOOST_BIND_CC *f) (B1, B2), A1 a1, A2 a2) { typedef BOOST_BIND_ST R (BOOST_BIND_CC *F) (B1, B2); typedef typename _bi::list_av_2<A1, A2>::type list_type; return _bi::bind_t<R, F, list_type> (f, list_type(a1, a2)); }
它根據調用時候咱們使用的參數推導出,函數返回值類型R, Functor類型 R (*f) (B1, B2),傳入的參數類型 A1, A2。
有了前面的這些分析,一些現象就很容易解釋了。好比爲何支持 _2, _1 交換順序等。
至於函數指針保存和調用的部分,代碼比較簡單;boost::bind爲支持成員函數作了特殊處理,由於成員函數的調用其實是instancePtr->foo(..),所以須要記錄instancePtr信息。這部分代碼在bind_mf_cc.hpp 和 mem_fn_template.hpp。
代碼比較簡單易讀,這裏就很少作分析了。
最後附上一張內部結構的圖,做爲本文的結束: