std::bind技能黑幕
副标题#e#
引子
最近群里较量热闹,各人都在山寨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的进程中我们还需要按照占位符来选择符合实参,即占位符要替换为挪用实参。
#p#副标题#e#
按照占位符来选择符合的实参
这个处所较量要害,因为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 // #p#分页标题#e#测试代码:
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。