boost中bind的使用

来源:互联网 发布:中云文化大数据靠谱吗 编辑:程序博客网 时间:2024/05/16 09:34

最近对boost的bind部分比较感兴趣,对其背后的机制进行了简单的分析,和大家分享一下。


注,我所看的代码是boost_1_64_0, 想来各个版本的差异不大。

定义函数

[cpp] view plain copy
  1. int f(int a, int b)  
  2. {  
  3.     return a + b;  
  4. }  
  5.   
  6. int g(int a, int b, int c)  
  7. {  
  8.     return a + b + c;  
  9. }  
调用范例:

bind(f, 1, 2)();                  //f(1,2)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)

_1, _2, ... _9在 boost中被称为placeholder,是占位符的意思。它表示参数。这种方式,我是只在boost中见过,是个非常神奇的用法。

它们究竟是什么呢?,且看定义:(boost/bind/placeholders.hpp)

[cpp] view plain copy
  1. boost::arg<1> _1;  
  2. boost::arg<2> _2;  
  3. boost::arg<3> _3;  
  4. boost::arg<4> _4;  
  5. boost::arg<5> _5;  
  6. boost::arg<6> _6;  
  7. boost::arg<7> _7;  
  8. boost::arg<8> _8;  
  9. boost::arg<9> _9;  
boost::arg也是个模板,至于是什么样的模板,留个悬念吧。

boost bind的这些功能,颠覆了我对C++的看法,从未想到过,C++还可以这么玩。那么,boost究竟是怎么实现的呢?


读者请注意,bind在这里涉及了两个参数表。第一个参数表是被bind绑定的函数(例子中f,g函数)的参数表,另外一个是bind生成的新的函数对象的参数表。

这两个参数表如何实现?如何转换是我们后面分析的重点。

bind是什么?

bind是函数,是非常神奇的函数,不是一个函数,而是一组函数,是一组重载的函数。

翻开代码 boost/bind/bind.hpp,找到BOOST_BIND字符串,大约在1290行的位置,boost定义了bind(1.51.0):

[cpp] view plain copy
  1. // bind  
  2.   
  3. #ifndef BOOST_BIND  
  4. #define BOOST_BIND bind  
  5. #endif  
  6.   
  7. // generic function objects  
  8.   
  9. template<class R, class F>  
  10.     _bi::bind_t<R, F, _bi::list0>  
  11.     BOOST_BIND(F f)  
  12. {  
  13.     typedef _bi::list0 list_type;  
  14.     return _bi::bind_t<R, F, list_type> (f, list_type());  
  15. }  
  16.   
  17. template<class R, class F, class A1>  
  18.     _bi::bind_t<R, F, typename _bi::list_av_1<A1>::type>  
  19.     BOOST_BIND(F f, A1 a1)  
  20. {  
  21.     typedef typename _bi::list_av_1<A1>::type list_type;  
  22.     return _bi::bind_t<R, F, list_type> (f, list_type(a1));  
  23. }  
  24.   
  25.   
  26. template<class R, class F, class A1, class A2>  
  27.     _bi::bind_t<R, F, typename _bi::list_av_2<A1, A2>::type>  
  28.     BOOST_BIND(F f, A1 a1, A2 a2)  
  29. {  
  30.     typedef typename _bi::list_av_2<A1, A2>::type list_type;  
  31.     return _bi::bind_t<R, F, list_type> (f, list_type(a1, a2));  
  32. }  
  33. ....  

太多了,只贴3个,足以说明问题。

模板参数

  • R 表示返回类型
  • F  表示函数指针的类型
  • A1,A2, .... 这些都是参数的类型
boost将BOOST_BIND定义为bind。所以,你看到的BOOST_BIND就是对bind函数的定义。

bind函数非常简单,它其实就是返回一个bind_t类的对象。bind_t类也是一个模板类。 如果仔细观察,你可以发现,这些不同参数的bind函数,其实现上不同在于list_av_N这一系列的辅助类是不同的。list_av_1表示一个参数,list_av_2表示两个参数, 以此类推。

这里的list_av_N对象,是第一个参数表,是函数F要求的参数表,这个参数表是可以包含placeholder的

list_av_N对象其实只是一个包装对象。我们以list_av_2为例:
[cpp] view plain copy
  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的作用,仅仅是为了包装而已。list_av_2::type == ist2<A1,A2> == list_type。

bind_t是什么东东?

奥秘在bind_t中,且看bind_t的定义 (也在boost/bind/bind.hpp中。下面只是bind_t的一种定义方法,但是道理都是一样的)
[html] view plain copy
  1. template<class R, class F, class L> class bind_t  
  2. {  
  3. public:  
  4.   
  5.     typedef bind_t this_type;  
  6.   
  7.     bind_t(F f, L const & l): f_(f), l_(l) {}  
  8.       
  9. #define BOOST_BIND_RETURN return  
  10. #include <boost/bind/bind_template.hpp>  
  11. #undef BOOST_BIND_RETURN  
  12.       
  13. };  
模板参数R代表return type, F代表function type, L表示的是listN(list0,list1,list2,....),这个是关键啊。

至于bind_template.hpp,这个源代码也比较简单,主要是定义了operator () 的实现。

bind_t重载括号运算符,因此,bind_t可以像函数那样调用。而且,bind_t的operator()有N多个重载,分别对应的是不同的参数类型和参数个数。这使得我们可以用不同的参数调用bind_t的对象。

我们摘抄一个有一两个参数的括号重载,看看
[cpp] view plain copy
  1. .....  
  2.     template<class A1> result_type operator()(A1 & a1)  
  3.     {  
  4.         list1<A1 &> a(a1);  
  5.         BOOST_BIND_RETURN l_(type<result_type>(), f_, a, 0);  
  6.     }  
  7. ....  
  8.   
  9.     template<class A1, class A2> result_type operator()(A1 & a1, A2 & a2)  
  10.     {  
  11.         list2<A1 &, A2 &> a(a1, a2);  
  12.         BOOST_BIND_RETURN l_(type<result_type>(), f_, a, 0);  
  13.     }  
  14. ......  

f_就是函数指针,这个不用多说;l_是 L (listN)对象。 

请注意,这里有两个listN出现:
  1. 一个是l_ 这是针对f_提供的参数列表,其中包含_1,_2,....这样的placeholder
  2. 一个是生成的临时变量 a, 这个是bind_t函数对象在调用时 的参数列表
之所以一直强调两个listN,是因为,奥秘就在listN这些个类中的。大家请记住这一点:一直存在两个listN对象

listN的奥秘

bind_t对象的operator () 调用的是listN 的operator (),那么,整个实现,就在listN中,为了方便说明,我们以list2为例。

对list2的分析,我们只看3部分:
1. 类声明部分
[cpp] view plain copy
  1. templateclass A1, class A2 > class list2: private storage2< A1, A2 >  
  2. {  
  3. private:  
  4.   
  5.     typedef storage2< A1, A2 > base_type;  
  6.   
  7. public:  
  8.   
  9.     list2( A1 a1, A2 a2 ): base_type( a1, a2 ) {}  
从这个定义,我们知道,它从storage2继承,storage2是什么?
[cpp] view plain copy
  1. template<class A1, class A2> struct storage2: public storage1<A1>  
  2. {  
  3.     typedef storage1<A1> inherited;  
  4.   
  5.     storage2( A1 a1, A2 a2 ): storage1<A1>( a1 ), a2_( a2 ) {}  
  6.   
  7.     template<class V> void accept(V & v) const  
  8.     {     
  9.         inherited::accept(v);  
  10.         BOOST_BIND_VISIT_EACH(v, a2_, 0);   
  11.     }     
  12.   
  13.     A2 a2_;  
  14. };  
从名字和定义上,我们就可以断定:storage2就是保存两个参数的参数列表对象。看来,storageN负责存储,而listN负责如何使用这些参数了。

2. operator[] 系列重载函数
[cpp] view plain copy
  1.     A1 operator[] (boost::arg<1>) const { return base_type::a1_; }  
  2.     A2 operator[] (boost::arg<2>) const { return base_type::a2_; }  
  3.   
  4.    .....  
  5.   
  6.     template<class T> T & operator[] (_bi::value<T> & v) const { return v.get(); }  
  7. .....  

我已经剔除了一些定义,只留下我们关系的定义。

这里面有两类定义,
  1. 针对 boost::arg<1>和boost::arg<2>定义的。其实就是针对_1, _2的定义,这个定义表明:如果是_1,那么,list2就返回存储的参数a1, 如果是_2,那么就返回存储的参数a2。这些参数,是上面我说的针对f_函数的参数;
  2. 针对_bi::value<T>的定义。_bi::value<T>就是对T进行简单封装的类型。这个定义仅仅是将value的值再取出来。
这两类定义,就是关键所在了。

3. operator()系列重载函数
[html] view plain copy
  1. ....  
  2.     template<class F, class A> void operator()(type<void>, F & f, A & a, int)  
  3.     {  
  4.         unwrapper<F>::unwrap(f, 0)(a[base_type::a1_], a[base_type::a2_]);  
  5.     }  
  6. ....  

尽管有多种不同的重载,但是基本形式就是这样的。
最后一个参数"int"我想没有直接的意义,可能是为了重载时用于区分不同的重载函数使用的。

unwrap的作用这里可以忽略。你可以认为就是直接调用f。

下面有两个不同寻常的语句:
[html] view plain copy
  1. a[base_type::a1_], a[base_type::a2_]  
a是一个listN对象,这两句究竟是什么意思呢?

下面,我们用两个例子分别说明,
例子1
bind(f, 1, 2)();                  //f(1,2)
下面,我们将参数代入:这种情况下,我们得到的bind_t对象,实际上是
[cpp] view plain copy
  1. bind_t<intint(*)(int,int), list2<int,int> >   
  2. //l_的类型和值  
  3. list2<int,int> = [ base_type::a1_ = 1, base_type::a2_ = 2]  
而bind(f, 1, 2) (); 中最后一个括号,调用了bind_t的operator () (void) : 
[cpp] view plain copy
  1. result_type operator()()  
  2. {  
  3.     list0 a;  
  4.     BOOST_BIND_RETURN l_(type<result_type>(), f_, a, 0);  
  5. }  
因此,a = list0。
在这种情况下,
[cpp] view plain copy
  1. a[base_type::a1_]  = =  
  2. a.operator[]((_bi::value<int>) 1)  
  3. == 1;  
  4.  a[base_type::a2_] ==  
  5. a.operator[](_bi::value<int>) 2)  
  6. == 2;  
其实listN里面的operator[](_bi::value<T>)就是打酱油的,目的就是为了在语法上统一。

因此,bind(f, 1, 2) () === f(1,2)的调用。

例子2
bind(f, _2, _1)(x, y);                 // f(y, x)
我们再把参数代入,这是,得到的bind_t对象就是
[html] view plain copy
  1. bind_t<int, int(*)(int, int), list2<boost::arg<2>, boost::arg<1> > >  
  2. //l_的类型和值是  
  3. list2<boost::arg<2>,boost::arg<1> > = [ base_type::a1_ = _2base_type::a2_ = _1]  
哈哈,看到了吧,list2的类型不一定要和F的参数类型一样的哦。

当调用bind(f, _2, _1)(x, y); 中bind_t::operator()(int, int) 的时候,即
[cpp] view plain copy
  1. template<class A1, class A2> result_type operator()(A1 & a1, A2 & a2)  
  2. {  
  3.     list2<A1 &, A2 &> a(a1, a2);  
  4.     BOOST_BIND_RETURN l_(type<result_type>(), f_, a, 0);  
  5. }  
此时,a的类型和值是
[cpp] view plain copy
  1. list2<intint> = [ base_type::a1_= x, base_type::a2_ = y]  
这种情况下,
[html] view plain copy
  1. a[l_.base_type::a1_] ==  
  2.   a [ _2 ] ==  
  3.  a.operator[] ( (boost::arg<2>&)_2) ==  
  4.  a.base_type::a2_ ==  
  5. y;  
  6.   
  7. a[l_.base_type::a2_] ==  
  8.   a [ _1 ] ==  
  9.  a.operator[] ( (boost::arg<1>&)_1) ==  
  10.  a.base_type::a1_ ==  
  11. x;  

即 bind(f, _2, _1)(x, y) === f(y, x);

关于_1,_2,_3,...._9

现在,我们要看看,到底 boost::arg<1>, boost::arg<2> ... boost::arg<9>是什么了。
[cpp] view plain copy
  1. templateint I > struct arg   
  2. {  
  3.     arg()  
  4.     {     
  5.     }     
  6.   
  7.     templateclass T > arg( T const & /* t */ )  
  8.     {     
  9.         // static assert I == is_placeholder<T>::value  
  10.         typedef char T_must_be_placeholder[ I == is_placeholder<T>::value? 1: -1 ];  
  11.     }     
  12. };  

呵呵,什么都没有!的确,什么都没有,因为它是placheholder嘛! 它只要表明自己的类型就可以了。这是为什么在 listN的operator[] 中,boost::arg<N> 没有定义形惨了。

第二个带有 T const & 参数的构造函数是什么?看起来很奇怪,其实,它是拷贝构造函数。

看看is_placeholder的定义吧:
[cpp] view plain copy
  1. templateint I > struct is_placeholder< arg<I> >  
  2. {  
  3.     enum _vt { value = I };  
  4. };  

它的作用,是防止错误的参考构造。
假如,你这样定义:
[cpp] view plain copy
  1. boost::arg<2> arg2(_1);  
模板展开后,将是这样的
[cpp] view plain copy
  1. struct arg <2>  
  2. {   
  3. .....  
  4.     arg( arg<1> const & /* t */ )  
  5.     {     
  6.         // static assert I == is_placeholder<T>::value  
  7.         typedef char T_must_be_placeholder[ I == is_placeholder<arg<1> >::value? 1: -1 ];  
  8.     }     
  9. };  
is_placeholder<arg<1> >::value == 1,而I == 2,因此,你会得到一个
[cpp] view plain copy
  1. typedef char T_must_be_placeholder[ -1 ];  
因此,你将收到一个编译错误。仅此而已。

其他

到此为止,boost bind的关键部分就已经清楚了。boost还有些高级议题,如类的成员函数的绑定、变量引用、绑定嵌套等等。这些议题都是以此为基础,再增加了一些新模板参数而已,已经不是实现的核心了,有兴趣的同学可以自己看看。
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 淘宝店铺显示服务竟然出错了怎么办 母羊下完羊羔把羊衣吃了怎么办? 移植后56天有黑色东西怎么办 我家的金丝熊浑身都是尿怎么办 一键启动车钥匙丢了怎么办 把爷爷的遗物弄丢了怎么办 如果你娶了一个傻子你怎么办 在国外订机票手机收不到信息怎么办 网上买机票名字写错了怎么办 买机票名字错了一个字怎么办 微店没收到货却显示已收货怎么办? 手机存的照片误删了怎么办 魔兽世界把要用的装备分解了怎么办 邻居家的狗见到我就叫怎么办 我的世界玩的时间长会卡应该怎么办 网易我的世界密码账号都忘了怎么办 我的世界创建世界画面乱码了怎么办 网易我的世界云端存档不够用怎么办 玩刺激战场带耳机声音有延迟怎么办 我的世界手机版狼变色怎么办 我的世界开了光影太阳太刺眼怎么办 我的世界饥饿值掉的慢怎么办 我的世界合装备过于昂贵怎么办 我的世界故事模式屏幕是黑的怎么办 人物只剩下轮廓的图用ps怎么办 两年义务兵考军校分数不够怎么办 大学生兵考上军校后原学籍怎么办 我的世界工业附魔到精准采集怎么办 交换生在台期间遗失通行证怎么办 驾驶证上的号码是士兵证号怎么办 士兵证丢了但是要买飞机票怎么办 君泰保安公司不发工资怎么办 冬天洗棉衣后有一圈白色怎么办 买了一批化肥没有执行标准怎么办 防护栏下面打不了膨胀螺丝怎么办 不知道怀孕照了x射线怎么办 腹部照了x光片照了三次怎么办 像在工厂戴的静电帽弄丢了怎么办 诈骗犯把钱被转到别人账户怎么办 狗狗5个月在家随地大小便怎么办 上课放屁放的快没憋到老是放怎么办