莫扎特传的剧情:泛型编程:源起、实现与意义

来源:百度文库 编辑:中财网 时间:2024/05/01 18:49:13
泛型编程:源起、实现与意义 --【转2】2011-04-19 23:21转载自 tianyamingyuedao最终编辑 tianyamingyuedao特化,图灵完备性,元编程C++的模板是支持特化的,这就给了它实现编译期控制结构的可能性,进而带来了一个图灵完备的子语言。模板特化的引入原本只是为了效率目的——针对不同的类型提供不同的实现。但后来却被发现能够实现编译期的if/else和递归等控制结构。 模板元编程最初由Erwin Unruh在1994年的一次会议上提出;当时他写了一个程序,在编译错误里面打印出一个素数序列。这个事件在C++历史上的地位就仿佛哥伦布发现新大陆。用Bjarne Stroustrup的话来说就是当时他当时和其他几个人觉得太神奇了。实际上,这个事情正标志了C++模板系统的图灵完备性被发现;后来Todd Veldhuizen写了一篇paper,用C++模板构建了一个元图灵机,从而第一次系统证明了C++模板的图灵完备性。接下来的事情就顺理成章了——一些ad hoc的模板元编程技巧不断被发掘出来,用于建造高效、高适应性的通用组件。最终,David Abrahams编写了boost库中的基本组件之一:Boost.MPL库。Boost.MPL以类型和编译期常量为数据,以模板特化为手段,实现了一个编译期的STL。你可以看到常见的vector,你可以看到transform算法,只不过算法操纵的对象和容器存放的对象不再是运行期的变量或对象,而是编译期的类型和常量。想知道模板元编程是如何用在库构建中的,可以打开一个Boost的子库(比如Boost.Tuple或Boost.Variant)看一看。 然而,毕竟C++的模板元编程是一门被发现而不是被发明的子语言。一方面,它在构建泛型库的时候极其有用。然而另一方面,由于它并非语言设计初始时考虑在内的东西,所以不仅在语法上面显得不那么first-class(比较笨拙);更重要的是,由于本不是一个first-class的语言特性,所以C++编译器并不知道C++元编程的存在。这就意味着,比如对下面这样一个编译期的if/else设施[8]: templatestruct if_ { typedef X type; // use X if b is true};templatestruct if_ {typedef Y type; // use Y if b is false}; typedef if_<(sizeof(Foobar)<40), Foo, Bar>::type type; 编译器并没有真的去进行if/else的分支选择,而是按部就班毫不知情地进行着模板的特化匹配。如果遇到Boost.MPL这样的模板元编程非常重的库,就会严重拖累编译速度,编译器进行了一重一重的特化匹配,实例化了一个又一个的模板实例,就是为了去获取里面定义的一个typedef,完了之后中间所有实例化出来的模板实例类型全都被丢弃[9]。 模板元编程最全面深入的介绍是Boost.MPL库的作者David Abrahams的《C++ Template Metaprogramming》,其中的样章(第三章)[10]对模板元编程作了一个非常高屋建瓴的概览[11]。 关于模板元编程,需要提醒的是,它并不属于“大众技术”。日常编程中极少需要用到元编程技巧。另一方面,C++模板里面有大量ad hoc的技巧,如果一头扎进去的话,很容易只见树木不见森林,所以需要提醒初学者的是,即便要学习,也要时刻保持“高度”,始终记得元编程的意义和目的,这样才不至于迷失在无穷无尽的语言细节中。 C++09——进化泛型编程在C++中取得了工业程度上的成功,得益于其高效性和通用性。但同时,在经过了近十年的使用之后,C++模板,这个作为C++实现泛型的机制的缺点也逐渐暴露出来。比如其中对于初学者最严重的一个问题就是在使用一个模板库时常常遇到无法理解的编译错误,动辄长达上K字节。这些编译错误很容易把一个初学者吓走。究其本质原因,为什么编译器会报出令人难以卒读的编译错误,是因为在编译器的眼里,只有类型,而没有“类型的类型”。比如说,迭代器就是一个类型的类型,C++里面也把它称为“概念”(Concept)。例如,std::sort要求参数必须是随机访问迭代器,如果你一不小心给了它一个非随机访问的迭代器,那么编译器不是抱怨“嘿!你给我的不是随机访问迭代器”,而是抱怨找不到某某重载操作符(典型的比如operator+(int))。因为在编译器眼里,没有“迭代器”这么个概念,这个概念只存在于程序员脑子里和STL的文档里。为了帮助编译器产出更友好的信息(当然,还有许多非常重要的其它原因[12]),C++09将对“类型的类型”加入first-class的支持,其带来的众多好处会将C++中的泛型编程带向一个新的高度:更友好、更实用、更直观。 此外,C++的模板元编程尚没有first-class的语言支持,一方面是因为其运用不像一般的模板编程这么广泛,因而没有这么紧急。另一方面,C++09的时间表也等不及一个成熟的提案。如果以后模板元编程被运用得越来越广泛的话,那first-class的语言支持是难免的。 总结本文对C++模板,以及C++模板所支持的泛型编程作了一个概览。着重介绍了泛型编程诞生的原因,泛型编程的过程和意义,与其它抽象手段的比较。并对C++中的模板元编程做了一些介绍。最后介绍了C++模板在C++09中的增强。 

[1] B. Stroustrup: A History of C++: 1979-1991. Proc ACM History of Programming Languages conference (HOPL-2). March 1993.[2] http://en.wikipedia.org/wiki/Alexander_Stepanov[3] http://www.cs.rpi.edu/~musser[4] 实际上,STL的区间概念被证明是一个不完美的抽象。你有没有发现,要传递一个区间给一个函数,如std::sort,你需要传递两个参数,一个是区间的 开头,一个是区间的末尾。这种分离的参数传递方式被证明是不明智的,在一些场合会带来使用上不必要的麻烦。比如你想迭代一组文件,代表这组文件的区间由一 个readdir_sequence函数返回,由于要分离表达一个区间,你就必须写:readdir_sequence entries(".", readdir_sequence::files);std::for_each(entries.begin(), entries.end(), ::remove);如果你只想遍历这个区间一次的话,你也许不想声明entries这个变量,毕竟多一个名字就多一个累赘,你也许只想:std::for_each(readdir_sequence(".", readdir_sequence::files), ::remove);下一代C++标准(C++09)会解决这个问题(将区间这个抽象定义为一个整体)。[5] 当然,语言并没有规定模板实例化的底层实现一定要是对每组参数类型组合实例化一个版本出来。但目前的实现,这种方案是最高效的。完全消除了抽象惩罚。另一 方面,One size fit all的方案也不是不可行,但总会有间接调用。这也正说明了静态类型系统的一个典型优点:帮助编译器生成更高效的代码。[6] http://en.wikipedia.org/wiki/Don't_repeat_yourself[7] http://en.wikipedia.org/wiki/Duck_typing[8] 摘自Bjarne Stroustrup的paper:Evolving a language in and for the real world: C++ 1991-2006[9] 也正因此,D语言加入了语言直接对模板元编程的支持,比如真正工作在编译期的static if-else语句。[10] http://www.boost-consulting.com/mplbook/[11] 第三章的翻译见我的blog:《深度探索元函数》http://blog.csdn.net/pongba/archive/2004/09/01/90642.aspx[12] 由于篇幅原因,这里无法展开详述Concept对C++泛型编程的其它重要意义,有兴趣的可以参见我的一篇blog:《C++0x漫谈系列 之:Concept! Concept!》。http://blog.csdn.net/pongba/archive/2007/08/04/1726031.aspx