关闭

boost::bind 源代码分析

标签: 代码分析listclassbi编译器fun
1828人阅读 评论(2) 收藏 举报

boost::bind库绝对是最有用,最有价值的库之一,已被纳入tr1。bind库的出现,替代了stl中的mem_fun,ptr_fun,bind1st,bin2nd等函数
本文并不介绍bind的使用,而是从bind的源代码(boost1.38.0)中分析出它的实现原理

bind执行大致可分为2个过程 1:构造函数对象 2:调用函数对象

下面我们以以下代码为列,抽丝剥茧,分析bind的内部机制。

以上代码在vc9下编译通过,演示了用bind执行了带1个参数的成员函数调用。bind大量使用了函数模板的重载机制,编译器根据参数信息找到合适的函数模板,在下面的分析过程中,我们只分析上面例子的相关代码,其他大同小异。


bind支持最多9个参数的函数调用(由于成员函数第一个参数是this指针,所以成员函数最多只支持8个参数),其实现原理是函数重载机制,以下是对应的bind源代码(bind_mf_cc.hpp)

  1. template<class R, class T,  
  2.     class B1,  
  3.     class A1, class A2>  
  4.     _bi::bind_t<R, _mfi::BOOST_BIND_MF_NAME(mf1)<R, T, B1>, typename _bi::list_av_2<A1, A2>::type>  
  5.     BOOST_BIND(R (BOOST_BIND_MF_CC T::*f) (B1), A1 a1, A2 a2)  
  6. {  
  7.     typedef _mfi::BOOST_BIND_MF_NAME(mf1)<R, T, B1> F;  
  8.     typedef typename _bi::list_av_2<A1, A2>::type list_type;  
  9.     return _bi::bind_t<R, F, list_type>(F(f), list_type(a1, a2));  
  10. }  

该模板函数包含5个模板参数,R是返回类型,T是成员函数的类型信息,B1是成员函数的参数类型,A1表示第一个参数,A2表示第2个参数。R T B1确定了函数指针的类型——R (BOOST_BIND_MF_CC T::*f) (B1).在编译器编译boost::bind(&test_class::fun,test,_1)(11);这段代码时进行类型推导,推导结果如下
R = void
T = test_class
B1 = int
A1 = test_class
A2 = arg<1>(这是个占位符,我们将在后面详细解释)
了解了函数定义之后,我们继续分析函数的实现:
首先是2个类型定义:
typedef _mfi::BOOST_BIND_MF_NAME(mf1)<R, T, B1> F;
typedef typename _bi::list_av_2<A1, A2>::type list_type;
F是函数类型,list_type是参数列表类型。最后一行保存函数指针和参数信息
return _bi::bind_t<R, F, list_type>(F(f), list_type(a1, a2));
这里需要注意的是list_type,即list_av_*::type(*表示1-9).list_av_2的定义如下(bind.hpp):

  1. template<class A1, class A2> struct list_av_2  
  2. {  
  3.     typedef typename add_value<A1>::type B1;  
  4.     typedef typename add_value<A2>::type B2;  
  5.     typedef list2<B1, B2> type;  
  6. };  

list_av_2<A1, A2>::type 类型为list2<B1, B2>。list*是一个关键的模板类,它负责存储参数信息,并调用函数。
参数信息是保存在storage*中的,一个listn(n:1-9)的继承关系如下(storage.hpp):
listn : private storagen : public storage(n-1):... public storage1
每个storage增加一个参数存储

现在,函数指针和参数类型都已经保存到bind_t函数对象中了,剩下的就只需要传人参数
函数调用部分,编译器会做以下几件事情:
1:推导相关模板参数,类型检查
2:根据类型信息找到最匹配函数模板,上面的例子会导致编译器找到以下的bind_t::operator()函数

  1. template<class A1> result_type operator()(A1 const & a1)  
  2. {  
  3.     list1<A1 const &> a(a1);  
  4.     BOOST_BIND_RETURN l_(type<result_type>(), f_, a, 0);  
  5. }  

list1<A1 const &> a(a1);将传入的参数数据保存到list1中。
对于BOOST_BIND_RETURN l_(type<result_type>(), f_, a, 0);初看语法比较怪异,这里解释一下,l_的定义在bind_template.hpp中,在bind.hpp中bind_t的类定义中包含了 bind_template.hpp:

  1. template<class R, class F, class L> class bind_t  
  2. {  
  3. public:  
  4.     typedef bind_t this_type;  
  5.     bind_t(F f, L const & l): f_(f), l_(l) {}  
  6. #define BOOST_BIND_RETURN return   
  7. #include <boost/bind/bind_template.hpp>   
  8. #undef BOOST_BIND_RETURN   
  9. };  

结合上面bind函数的定义:

  1. template<class R, class T,  
  2.     class B1,  
  3.     class A1, class A2>  
  4.     _bi::bind_t<R, _mfi::BOOST_BIND_MF_NAME(mf1)<R, T, B1>, typename _bi::list_av_2<A1, A2>::type>  
  5.     BOOST_BIND(R (BOOST_BIND_MF_CC T::*f) (B1), A1 a1, A2 a2)  
  6. {  
  7.     typedef _mfi::BOOST_BIND_MF_NAME(mf1)<R, T, B1> F;  
  8.     typedef typename _bi::list_av_2<A1, A2>::type list_type;  
  9.     return _bi::bind_t<R, F, list_type>(F(f), list_type(a1, a2));  
  10. }  

根据bind_t的构造来看,这里l_即list_type(a1, a2),类型为list2。
由于是成员函数指针,最终导致调用list2::operator()(如果是普通函数指针,则调用list1::operator())

  1. template<class F, class A> void operator()(type<void>, F & f, A & a, int)  
  2. {  
  3.     unwrapper<F>::unwrap(f, 0)(a[base_type::a1_], a[base_type::a2_]);  
  4. }  

其中base_type即storage2,a1_即成员函数的第一个参数——this指针(该参数在构造bind_t函数对象的时候就保存下来了),a2_为该成员函数的第2个参数,在上面的例子中,即我们传人的参数11


最后再来看看占位符(_1,_2)的实现,在placeholders.hpp中定义了9个占位符:

  1. static boost::arg<1> _1;  
  2. static boost::arg<2> _2;  
  3. static boost::arg<3> _3;  
  4. static boost::arg<4> _4;  
  5. static boost::arg<5> _5;  
  6. static boost::arg<6> _6;  
  7. static boost::arg<7> _7;  
  8. static boost::arg<8> _8;  
  9. static boost::arg<9> _9;  

arg在arg.hpp中定义:

  1. templateint I > struct arg  
  2. {  
  3.     arg()  
  4.     {  
  5.     }  
  6.     templateclass T > arg( T const & /* t */ )  
  7.     {  
  8.         // static assert I == is_placeholder<T>::value   
  9.         typedef char T_must_be_placeholder[ I == is_placeholder<T>::value? 1: -1 ];  
  10.     }  
  11. };  

除了一个模板构造函数之外,好像它什么都不做,那么占位符有什么用呢?
1:它改变了参数的个数,编译器可根据参数信息选择重载函数
2:它确定了类型信息,在上述例子中list2中a2的类型为arg<1>,arg的模板构造函数会做类型检查
3:它可以改变参数调用的顺序:bind(f,_2,_1)(11,22) = f(22,11) 其实现是在list*::operator()函数里做了一次间接转换:

  1. template<class F, class A> void operator()(type<void>, F & f, A & a, int)  
  2. {  
  3.     unwrapper<F>::unwrap(f, 0)(a[base_type::a1_], a[base_type::a2_]);  
  4. }  

再看看storage2的定义:

  1. template<class A1, class A2> struct storage2: public storage1<A1>  
  2. {  
  3.     typedef storage1<A1> inherited;  
  4.     storage2( A1 a1, A2 a2 ): storage1<A1>( a1 ), a2_( a2 ) {}  
  5.     template<class V> void accept(V & v) const  
  6.     {  
  7.         inherited::accept(v);  
  8.         BOOST_BIND_VISIT_EACH(v, a2_, 0);  
  9.     }  
  10.     A2 a2_;  
  11. };  

 

当编译bind(f,_2,_1)(11,22)时,编译器推导出base_type::a1_类型为arg<2>,base_type::a2_类型为arg<1>,再看list的[]重载实现:
    A1 operator[] (boost::arg<1>) const { return base_type::a1_; }
    A2 operator[] (boost::arg<2>) const { return base_type::a2_; }
第一个参数a[base_type::a1_] -> a[arg<2>] -> a2_
第二个参数a[base_type::a2_] -> a[arg<1>] -> a1_
这样函数调用就变成了
unwrapper<F>::unwrap(f, 0)(a2_, a1_);

0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:501086次
    • 积分:5344
    • 等级:
    • 排名:第5043名
    • 原创:42篇
    • 转载:201篇
    • 译文:1篇
    • 评论:37条
    文章分类
    最新评论