写在前面的话
在C++11之后,std::bind是C++标准库的一个组件了。一开始想弄个C++11的实现来研究下,发现里面用到了可变参数模板(代码变得非常神奇).
http://llvm.org/svn/llvm-project/libcxx/trunk/include/functional
还是弄个原始点的boost的实现来研究下。
话说网上关于boost::bind的实现的文章也有不少,不过大多数都是贴一段代码,再扯一通,结果到头来什么都没看明白。(起码LZ是。。)
花了一天的功夫,最终从boost::bind的源代码中抠出了可编绎运行的代码。
下面一步一步来解析boost::bind的实现。
标准库中的fouctor和bind1st的实现
首先从简单的入手。先看下标准库中的fouctor和bind1st的实现(为了防止和标准库的命名冲突,全部都在命名后面加了2)。
1 |
|
上面的实现还是比较简单的,希望还没有看晕。:)
从代码可以看出binder1st的实现,实制上是把参数保存起来,等到调用时,再取出来使用。
boost::bind从原理上来说,也是这么回事,但是要复杂得多。
解析简化版bind2
下面是从boost::bind源码中抠出来的,一个简单的bind的实现(bind改为bind2),主流编译器应该都可以编统执行。
为了方便理解程序运行过程,代码中增加了一些cout输出。
myBind.h:
1 |
|
main.cpp:
1 |
|
在上面的代码中有很多个类,有几个是比较重要的any,storage,list和bind_t。
先来看类any:
1 | template<int I> struct arg { |
在上面的代码中,我们看到了_1和_2,没错,所谓的占位符就是这么个东东,在匿名名字空间中定义的空的struct。
接下来是storage1,storage2类:
1 | template<class A1> struct storage1 { |
可以看出storage类只是简单的继承关系,实际上storage类是用来存储bind函数所传递进来的参数的值的,之所以采用继承而不用组合,其实有精妙的用处。
接着看类list1和list2,它俩分别是storage1和storage2的子类(即参数是存放在listN类中的):
1 | template<class A1> class list1: private storage1<A1> { |
仔细看代码,可以看到 list1和list2重载了operator [ ] 和operator () 函数,这些重载函数很关键,下面会谈到。
再看类bind_t:
1 | template<class R, class F, class L> class bind_t { |
同样,我们可以看到bind_t类重载了operator ()函数,实际上,bind_t类是bind函数的返回类型,bind_t实际上是一个stl意义上的funtor。
注意bind_t的两个成员F f_ 和 L l_,这里是关键之处。
介绍完关键类,下面以main.cpp中下面部分代码为例,详细说明它的工作流程。
1 | int i1 = 1, i2 = 2; |
首先bind2函数返回一个bind_t类,这个类中的F成员,保存了tow_arguments函数指针,L成员(即list2类),保存了参数123 和 _1。
bind_t类采用的是以下的特化:
1 | template<class R, class B1, class B2, class A1, class A2> |
其中storage2类(list2的父类)和storage1类,分别采用的是以下的特化:
1 | template<class A1, int I> struct storage2<A1, boost::arg<I> > : public storage1<A1> |
1 | template<class A1> struct storage1 |
(这里可以试下把123和_1互换位置,就能发现storage系列类用继承的精妙之处了,这里不展开了)
当bind_t调用operator (i1, i2)函数时,即以下函数:
1 | template<class A1, class A2> result_type operator()(A1 & a1, A2 & a2) { |
再次生成一个list2,这个list2中保存的是i1 和 i2的值。接着调用l_.operator() (type<result_type>(), f_, a, 0)
函数(还记得l_是什么东东不?)
l_实际是上刚才保存了123和1的list2!而f是由bind函数据绑定的函数的指针!
接着看list2的operator() 函数的实现:
1 | template<class R, class F, class A> R operator()(type<R>, F & f, A & a, |
可以看到f是bind所绑定的函数的指针,即这里是调用了我们所绑定的函数。那么参数是从哪里来的?
注意A &a实际上是保存了i1和i2的list2!,所以这里又调用了list2的operator[ ]函数!
再看下list2的operator[] 函数的实现:
1 | A1 operator[](boost::arg<1>) const { |
貌似问题都解决了,其实这里还有一个关键要素,两个list2是怎么合并起来,组成正确的参数传递给函数f的?(第一个list2存放了123和_1,第二个list2存放了i1和i2)
传递给tow_arguments函数的参数实际是是(123, 1)!
这里要仔细研究这些operator[]函数的实现,才能真正理解其过程。
注意,上面的的代码中bind2只能绑定普通的函数,不能绑定类成员函数,functor,智能指针等。不过其实区别不大,只是增加一些特化的代码而已。
还有一些const相关的函数删掉了。
后记
从代码可以看到boost::bind和原生的函数指针相比,会损失效率(两次的寻址,函数参数的拷贝,有一些可能没有inline的函数调用)。
不得不吐槽下boost的代码中那些神奇的宏,如果不是IDE有提示,我想真心弄不明白到底哪段是有意义的。。有时候还很神奇地include一部分代码进来(不是头文件,只是实现的一部分!)。
不得不吐槽下模板编程中的const,为了一个函数,比如int sum(int a, int b); 就得写四个重载函数来对应不同参数是或者不是const的情况。所以大家可以想像bind最多支持9个参数,那么有多少种情况了。
boost::bind的实现的确非常精巧,隐藏了很多复杂性。在《C++沉思录》中作者说到世界是复杂的,可以通过复杂性获取简单性。也许这个是对的,但是对于无数的后来的程序员总会有人想要看看黑盒子里到底是什么东东,结果总要花大量的时间才能理解,才能获得这种“简单性”。
C++的模板代码中最痛苦的是到处都是typedef,一个typedef就把所有的类型信息都干掉了!而这东东又是必须的。
也许C++给编程语言技术带来的最大贡献就是牛B的编译器了!
C++11中貌似部分的支持concept,希望这个能简化模板编程。