泛化的bind实用性很强,支持将泛化函数(函数、函数指针、仿函数、与成员函数)与运行期实参的动态绑定;在实际工程中也经常作为基础工具集之一被频繁地使用;我计划投入一定的精力,仔细阅读boost库中的基础工具集的代码实现(bind是第一个);旨在了解这些工具的内部实现机理同时也提高下自己的C++语言泛型编程技术,以备后续的实际工程中使用;
2005年,Andrei Alexandrescu就在他的C++巨著《C++设计新思维中》中的5.10节中提出了泛型化的函数绑定;bind的实现目标非常明确,就是在运行期实现针对泛化函数任意类型、任意个数的实参进行绑定;形式如下:
bind(f, _2, _1)(x, y); // f(y, x)
bind(g, _1, 9, _1)(x); // g(x, 9, x)
bind(g, _3, _3, _3)(x, y, z); // g(z, z, z)
bind(g, _1, _1, _1)(x, y, z); // g(x, x, x)
根据上述的形式,假如对泛化函数不考虑的话,至少涉及任意个参数(理论上),任意类型、任意位置的实参与函数绑定;另外,涉及占位符等,值拷贝、引用或者移动参数传递、返回值等细节技术问题。
我们一步一步先进行拆解;从上述调用的形式分析,bind函数是function template,其最重要的作用是(个人认为)是进行参数类型推导;function template会根据不同的参数个数,而被重载;实现代码中支持最多9个参数;代码如下:(此处列出的是两个参数的函数模板)
template<class R, class F, class A1, class A2> _bi::bind_t<R, F, typename _bi::list_av_2<A1, A2>::type> BOOST_BIND(F f, A1 a1, A2 a2) { typedef typename _bi::list_av_2<A1, A2>::type list_type; return _bi::bind_t<R, F, list_type> (f, list_type(a1, a2)); }1、 根据函数重载规则(9个实参范围内, A表示参数,英语Argument,A1, A2, …… A9分表表示9种实参类型,当然9个实参类型可重复,可相同)选择对应参数个数的bind函数模板会别Instantiation。此过程根据函数模板的特性,自然地推导出参数A1,A2;
2、被推导出的参数类型A1, A2,通过class template _bi::list_av_2 组成了一个由两个类型元素合成的类型列表;新组成的类型列表被typedef成list_type类型
3、list_type(a1, a2) 被显式地的调用构造函数;
4、bind的返回类型为 _bi::bind_t<R, F, list_type>; 且实际真正实现是通过调用class template _bi::bind_t的函数调用(operator () .... )的形式实现的。bind_t是被重载了的仿函数!
5、值得注意的是:F类型可以通过bind的调用,被推导出来;但是返回类型R, 目前还是推导不出来哦!
接下来,我们先看class template _bi::list_av_2的实现代码;
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_; }; namespace _bi { // add_value // 参数类型是占位符时,add_value<T>::type是boost::arg<I>;与类型T无关了 template< class T, int I > struct add_value_2 { typedef boost::arg<I> type; }; //参数类型非占位符时,add_value<T>::type实际就是_bi::value<T> template< class T > struct add_value_2< T, 0 >{ typedef _bi::value< T > type; }; template<class T> struct add_value{ typedef typename add_value_2< T, boost::is_placeholder< T >::value >::type type; }; template<class T> struct add_value< value<T> >{ typedef _bi::value<T> type; }; template<class T> struct add_value< reference_wrapper<T> >{ typedef reference_wrapper<T> type; }; template<int I> struct add_value< arg<I> >{ typedef boost::arg<I> type; }; template<int I> struct add_value< arg<I> (*) () >{ typedef boost::arg<I> (*type) (); }; template<class R, class F, class L> struct add_value< bind_t<R, F, L> >{ typedef bind_t<R, F, L> type; }; } template<class A1, class A2> struct list_av_2 { typedef typename add_value<A1>::type B1; typedef typename add_value<A2>::type B2; typedef list2<B1, B2> type; };
这段代码中,主要就是理解add_value类模板针对各种情况的特化;单独看代码貌似比较难理解;以例子代入的话,会容易些;调用实例如下述代码:
#include <iostream> struct sum { typedef double result_type; double operator()(int a, double b) { return a+b; } }; int main(int argc, char *argv[]) { // 情况1 std::cout << boost::bind(sum(), _1, _2)(3, 4.0) << std::endl; // 情况2 std::cout << boost::bind(sum(), 3, 4.0)() << std::endl; // 情况3 std::cout << boost::bind(sum(), _1, 4.0)(3) << std::endl; return 0; }
情况1:A1: boost::arg<1>, A2: boost::arg<2>; B1: boost::arg<1>, B2: boost::arg<2>;
list_type = list2<B1, B2>
情况2:A1: int, A2: double; B1: value<int>, B2: value<double>;
list_type = list2<B1, B2>
情况3:A1: boost::arg<1>, A2: double; B1: boost::arg<1>, B2: value<double>;
list_type = list2<B1, B2>
根据argument deduction的结果进行分析,本次进行参数推导的参数列表是第一对圆括号内的。第二个实参列表并木有丢失!我们把第一对圆括号内的参数,简称第一参数列表;第二对圆括号内的参数,简称第二参数列表;注意,第二参数列表才是真正的实际参数;第一参数列表里可能包含占位符参数;
template< class A1, class A2 > class list2: private storage2< A1, A2 > { private: typedef storage2< A1, A2 > base_type; public: list2( A1 a1, A2 a2 ): base_type( a1, a2 ) {} A1 operator[] (boost::arg<1>) const { return base_type::a1_; } A2 operator[] (boost::arg<2>) const { return base_type::a2_; } A1 operator[] (boost::arg<1> (*) ()) const { return base_type::a1_; } A2 operator[] (boost::arg<2> (*) ()) const { return base_type::a2_; } template<class T> T & operator[] (_bi::value<T> & v) const { return v.get(); } template<class T> T const & operator[] (_bi::value<T> const & v) const { return v.get(); } template<class T> T & operator[] (reference_wrapper<T> const & v) const { return v.get(); } template<class R, class F, class L> typename result_traits<R, F>::type operator[] (bind_t<R, F, L> & b) const { return b.eval(*this); } template<class R, class F, class L> typename result_traits<R, F>::type operator[] (bind_t<R, F, L> const & b) const { return b.eval(*this); } template<class R, class F, class A> R operator()(type<R>, F & f, A & a, long){ return unwrapper<F>::unwrap(f, 0)(a[base_type::a1_], a[base_type::a2_]); } template<class R, class F, class A> R operator()(type<R>, F const & f, A & a, long) const { return unwrapper<F const>::unwrap(f, 0)(a[base_type::a1_], a[base_type::a2_]); } 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_]); } template<class F, class A> void operator()(type<void>, F const & f, A & a, int) const{ unwrapper<F const>::unwrap(f, 0)(a[base_type::a1_], a[base_type::a2_]); } template<class A> bool operator()( type<bool>, logical_and & /*f*/, A & a, int ){ return a[ base_type::a1_ ] && a[ base_type::a2_ ]; } template<class A> bool operator()( type<bool>, logical_and const & /*f*/, A & a, int ) const{ return a[ base_type::a1_ ] && a[ base_type::a2_ ]; } template<class A> bool operator()( type<bool>, logical_or & /*f*/, A & a, int ){ return a[ base_type::a1_ ] || a[ base_type::a2_ ]; } template<class A> bool operator()( type<bool>, logical_or const & /*f*/, A & a, int ) const{ return a[ base_type::a1_ ] || a[ base_type::a2_ ]; } template<class V> void accept(V & v) const{ base_type::accept(v); } bool operator==(list2 const & rhs) const{ return ref_compare(base_type::a1_, rhs.a1_, 0) && ref_compare(base_type::a2_, rhs.a2_, 0); } };
list0, list1, list2, ... ... list9 是不同模板参数个数的listN 类模板实现;上述同样以list2为例说明list的实现;这个listN的类模板实现很重要,因为在接下来的真正被绑定的函数调用体被调用时_bi::bind_t<>::operator()(...),最终还是依赖于这个listN的实现;
listN因为负责最终存储传入的第二参数列表中的实参,就必然有随机读取任意参数的功能;除此之外,listN还负责最终的绑定函数体的调用;因此listN的实现,主要有三部分功能:
(1)存储最终调用函数体需要传入的参数;即,存储第二参数列表;
(2)operator[] 的运算符的重载;【类似随机迭代器,对保存的实参列表进行随机访问】
(3)operator() 运算符的重载; 【实现最终被绑定的泛化函数体的函数调用】
功能(1)是listN通过storageN的私有继承得以实现;此处的继承使用了私有继承,私有继承的实现语义是”用……来实现“(“is implemented in terms of ");且子类在其成员函数里调用基类公有成员时,使用base::member_语法; 按照上述调用代码逻辑情况1为例,传入参数a1,a2分别是 _1, _2,被存储在storageN里。
namespace boost { namespace _bi { // 1 template<class A1> struct storage1 { explicit storage1( A1 a1 ): a1_( a1 ) {} template<class V> void accept(V & v) const { BOOST_BIND_VISIT_EACH(v, a1_, 0); } A1 a1_; }; #if !defined( BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION ) && !defined( __BORLANDC__ ) template<int I> struct storage1< boost::arg<I> > { explicit storage1( boost::arg<I> ) {} template<class V> void accept(V &) const { } static boost::arg<I> a1_() { return boost::arg<I>(); } }; template<int I> struct storage1< boost::arg<I> (*) () > { explicit storage1( boost::arg<I> (*) () ) {} template<class V> void accept(V &) const { } static boost::arg<I> a1_() { return boost::arg<I>(); } }; #endif // 2 template<class A1, class A2> struct storage2: public storage1<A1> { typedef storage1<A1> inherited; storage2( A1 a1, A2 a2 ): storage1<A1>( a1 ), a2_( a2 ) {} template<class V> void accept(V & v) const { inherited::accept(v); BOOST_BIND_VISIT_EACH(v, a2_, 0); } A2 a2_; }; #if !defined( BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION ) template<class A1, int I> struct storage2< A1, boost::arg<I> >: public storage1<A1> { typedef storage1<A1> inherited; storage2( A1 a1, boost::arg<I> ): storage1<A1>( a1 ) {} template<class V> void accept(V & v) const { inherited::accept(v); } static boost::arg<I> a2_() { return boost::arg<I>(); } }; template<class A1, int I> struct storage2< A1, boost::arg<I> (*) () >: public storage1<A1> { typedef storage1<A1> inherited; storage2( A1 a1, boost::arg<I> (*) () ): storage1<A1>( a1 ) {} template<class V> void accept(V & v) const { inherited::accept(v); } static boost::arg<I> a2_() { return boost::arg<I>(); } }; #endif
上述截取了部分storageN的类模板代码,可以看出storageN是个单继承体系,N表示了继承体系的总层级,例如storage2则表示整体继承体系一共2层,第一层存储了参数a1, 第二层公有继承于storage1,存储参数a2;至于为什么使用这样的实现方式进行存储,后续再深入研究;另外需要注意的是,是A1,A2的类型决定了参数存储方式;如果const reference&, 实参是以引用的形式存储的。后续值得关注下,A1,A2在经过函数模板的类型推导后,是否存有中间加工的元编程处理过程;
功能(3)的实现中使用了unwrapper类模板对F的外覆类进行解外覆处理;ref.hpp实际是boost中的另外一个基础核心工具集之一;后续单独研究下;
接下来,就是重点分析下,如何从占位符到实参的绑定,并且bind_t的对绑定函数的实际调用;这里自然要先粘贴下_bi::bind_t代码的实现摘要;
template< class A > struct list_add_cref { typedef A const & type; }; template< class A > struct list_add_cref< A& >{ typedef A & type; }; template<class R, class F, class L> class bind_t { private: F f_; L l_; public: typedef typename result_traits<R, F>::type result_type; typedef bind_t this_type; bind_t( F f, L const & l ): f_( f ), l_( l ) {} ... ... template<class A1, class A2> result_type operator()( A1 && a1, A2 && a2 ) { list2< typename list_add_cref<A1>::type, typename list_add_cref<A2>::type > a( a1, a2 ); return l_( type<result_type>(), f_, a, 0 ); } template<class A1, class A2> result_type operator()( A1 && a1, A2 && a2 ) const { list2< typename list_add_cref<A1>::type, typename list_add_cref<A2>::type > a( a1, a2 ); return l_( type<result_type>(), f_, a, 0 ); } …… …… 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); } template<class A1, class A2> result_type operator()(A1 & a1, A2 & a2) const { list2<A1 &, A2 &> a(a1, a2); BOOST_BIND_RETURN l_(type<result_type>(), f_, a, 0); } …… ……
上述第一参数列表推导后,加工处理后的组成的list_type类型,被推导成L类型;同时,根据第二参数列表的个数及类型,会找对应参数个数的operator()实例化的调用;第二参数列表会被推到成类型A1,A2 ....并组成另外一个listN类型且该类型的实例是a;如前所述,在组成listN的过程中,通过list_add_cref<T>元编程处理过程针对A1,A2推导的类型进行add const reference处理,以确保实参以最低成本存储在storageN中。L类型中的多个operator()的重载形式之一被调用时,list_type的operator[]实现了最终的占位符到实参(第二参数列表中的)的绑定;