引子
最近群裡比較熱鬧,大家都在山寨c++11的std::bind,三位童孩分別實現了自己的bind,代碼分別在這裡:
木頭雲的實現:串連稍後補上。
mr.li的實現:https://code.google.com/p/y-code-svn/source/browse/
null的實現:http://www.cnblogs.com/xusd-null/p/3693817.html
這些實現思路和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的過程中我們還需要根據預留位置來選擇合適實參,即預留位置要替換為調用實參。