程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> std::bind技術內幕

std::bind技術內幕

編輯:C++入門知識

引子

  最近群裡比較熱鬧,大家都在山寨c++11的std::bind,三位童孩分別實現了自己的bind,代碼分別在這裡:

  • 木頭雲的實現:http://sourceforge.net/projects/foobar-dummy/files/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 template <typename T> struct is_pointer_noref : std::is_pointer<typename std::remove_reference<T>::type> {}; template <typename T> struct is_memfunc_noref : std::is_member_function_pointer<typename std::remove_reference<T>::type> {}; template <typename R, typename F, typename... P> inline typename std::enable_if<is_pointer_noref<F>::value, R>::type invoke(F&& f, P&&... par) { return (*std::forward<F>(f))(std::forward<P>(par)...); } template <typename R, typename F, typename P1, typename... P> inline typename std::enable_if<is_memfunc_noref<F>::value && is_pointer_noref<P1>::value, R>::type invoke(F&& f, P1&& this_ptr, P&&... par) { return (std::forward<P1>(this_ptr)->*std::forward<F>(f))(std::forward<P>(par)...); } template <typename R, typename F, typename P1, typename... P> inline typename std::enable_if<is_memfunc_noref<F>::value && !is_pointer_noref<P1>::value, R>::type invoke(F&& f, P1&& this_obj, P&&... par) { return (std::forward<P1>(this_obj).*std::forward<F>(f))(std::forward<P>(par)...); } template <typename R, typename F, typename... P> inline typename std::enable_if<!is_pointer_noref<F>::value && !is_memfunc_noref<F>::value, R>::type invoke(F&& f, P&&... par) { return std::forward<F>(f)(std::forward<P>(par)...); } template<typename Fun, typename... Args> struct Bind_t { typedef typename decay<Fun>::type FunType; typedef std::tuple<typename decay<Args>::type...> ArgType; typedef typename result_traits<FunType>::type result_type; public: template<class F, class... BArgs> Bind_t(F& f, BArgs&... args) : m_func(f), m_args(args...) { } template<typename F, typename... BArgs> Bind_t(F&& f, BArgs&&... par) : m_func(std::move(f)), m_args(std::move(par)...) {} template <typename... CArgs> result_type operator()(CArgs&&... args) { return do_call(MakeIndexes<std::tuple_size<ArgType>::value>::type(), std::forward_as_tuple(std::forward<CArgs>(args)...)); } template<typename ArgTuple, int... Indexes > result_type do_call(IndexTuple< Indexes... >& in, ArgTuple& argtp) { return simple::invoke<result_type>(m_func, select(std::get<Indexes>(m_args), argtp)...); //return m_func(select(std::get<Indexes>(m_args), argtp)...); } private: FunType m_func; ArgType m_args; }; template <typename F, typename... P> inline Bind_t<F, P...> Bind(F&& f, P&&... par) { return Bind_t<F, P...>(std::forward<F>(f), std::forward<P>(par)...); } template <typename F, typename... P> inline Bind_t<F, P...> Bind(F& f, P&... par) { return Bind_t<F, P...>(f, par...); } View Code

測試代碼:

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); } View Code

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。

 

如果你覺得這篇文章對你有用,可以點一下推薦,謝謝。

c++11 boost技術交流群:296561497,歡迎大家來交流技術。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved