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。
代码比较简单易读,这里就很少作分析了。
最后附上一张内部结构的图,做为本文的结束: