C++0x热点问题访谈

C++0x热点问题访谈 
2004 年底前后,经过较长一段时间的沉默,一批世界级的C++著作相继面世。2005年4月,在挪威Lillehammer举行的C++标准委员会会议上,Bjarne Stroustrup促成委员会达成一致意见:让C++0x中的x等于9。2005年11月,Bjarne Stroustrup、Herb Sutter、Stanley B. Lippman、Andrei Alexandrescu等前辈、新锐将在Las Vegas庆祝C++ 廿周年。2005年底,C++中国社群将在上海举办首届“现代C++设计和编程”技术大会……C++好戏连台,令人振奋。笔者近日就C++0x以及其他一些热点问题请教了Bjarne先生。大师观点,不敢专美,整理成文,以飨同好。 
C++0x 
荣耀: Library TR1 (library Technical Reports,库技术报告)和TR2的动机是什么?TR1和TR2、TR1/TR2和C++0x、Performance TR(Performance Technical Reports,性能技术报告)和C++0x、Boost和C++0x之间的关系如何? 
Bjarne Library TR 是对标准库改进工作的具体结果的体现。当一套设施、特性就绪后,TR1即被表决通过,然后人们继续向TR2前进。TR1和TR2之间的区别仅在于“时间不同,所做的事情不同”而已。大部分TR1和TR2中的内容有望成为C++0x的一部分。 
Performance TR 是一个关于C++适合于性能严苛和资源受限的编程的报告,大多和嵌入式系统编程有关。其作用主要在于教育程序员并努力驱散萦绕C++的能与不能的流言蜚语。特别要指出的是,该TR证明C++是一门极好的适合于嵌入式系统编程的语言。 
启动Boost项目的人们(著名的如Beeman Dawes)过去是、现在仍然是C++标准委员会的成员。Boost的目标是提供对标准库的扩展,并使得大多数有用且成功的Boost库成为新一代C++标准。其中一部分(而非全部)将会成为C++0x的一部分。注意,当一个库被添加进标准时,往往需要对其进行某种程度的修改。许多TR1扩展一开始都是作为Boost的一个组成部分而开始它们的生命旅程的。 
荣耀: 我们经常听到C++在不损及效率的前提下达成抽象和优雅,在C++0x的演化(设计)中同样如此,您能从技术的层面来谈谈C++(0x)是如何做到这一点的吗? 
Bjarne 这个问题说来话长,需要专门写一篇论文。不过基本的思想一如既往:遵从零开销原则(参见D&E),拥有内建的直接到硬件的操作映射,以及提供强大灵活的静态类型系统。 
荣耀注:D&E是Bjarne写的一本关于C++语言设计原理、设计决策和设计哲学的专著。该书全名是《The Design and Evolution of C++》。零开销原则的含义是:无需为未使用的东西付出代价。详情参见D&E。
荣耀: 您最近提到在C++0x标准化过程中,委员会奉行的若干原则中有这样一条:只做可以改变人们思考方式的改变,请问此原则的出发点是什么? 
荣耀注:D&E中并没有明确地提及这条设计原则,但Bjarne在最近的一篇文章里特别提到了这一点,故有此一问。
Bjarne 任何改变都会带来在实现、学习等方面的代价。对一个程序员编写具体哪一行代码的方式的改进不会带来太大的好处,能够改进程序员解决问题和组织程序的方式才可以。面向对象和泛型编程已经改变了很多人的思考方式,这也是很多C++语言设施支持这些风格的用意。因此,对于语言和库设计者来说,最好将时间花在有助于改变人们思考方式的设施和技术之上。 
荣耀: 如果让您分别列出可能会进入C++0x标准的Top 5语言新特性和库扩展,您愿意列出哪些? 
Bjarne 语言特性包括:
²         Concepts
²         一般化的初始化(generalized initialization)
²         对泛型编程的更好的支持(auto、decltype等)
²         内存模型
²         …… 
荣耀注:Concepts在泛型编程中具有极其重要的地位,但C++98并未对concepts提供直接的支持,它们只是以文档的形式连同一套松散的编程约定而存在。目前STL中的concept的作用仅限于在模板实例化时对模板参数进行检查,然而,如果某个函数模板参数不符合某个concept,编译器并不能对该函数模板的定义进行及早的强类型纠错。尽管Bjarne先生一向对语言扩展持保守态度,但在为concept提供语言层面的直接支持方面却一向积极,实际上,他本人正是为C++加入concept的最早的提案人之一。几乎可以肯定该语言特性会被加入到C++0x之中。
荣耀注:关于“一般化的初始化”问题可参见后面的问答。内存模型也就是后面所说的机器模型。auto和decltype则分别是计划加入C++0x的新关键字和操作符,它们均可用于改善泛型编程。
库设施则有:
²         unordered_maps (即哈希表)
²         正则表达式匹配
²         线程
²         ……
我不愿意勉强向这种“top N”列表中添加更多的东西,关于还有哪些主要的东西应该被添加进来,目前尚存在激烈的讨论。 
荣耀: 您说过,“在C++0x的concept的设计中存在的最大的困难是维持模板的灵活性。我们不要求模板参数适合于类层次结构或要求所有操作都能够通过虚函数进行访问(就象Java和C#的“generics”所做的那样)”。可以详谈一下吗? 
Bjarne 在“generics”中,参数必须是派生于在generic定义中所指定的接口(C++中的等价物则是“抽象类”)。这就意味着所有generic参数类型必须适合于某个类层次结构。举个例子,如果你编写了一个generic,我定义一个class,人们无法使用我的class作为你的generic的参数,除非我知道你指定的接口,并使该class派生于它。这种做法太呆板。当然了,对此问题有迂回解决办法,但需要编写复杂的代码。 
另一个问题是,因为内建的类型(比如int)不是类,它们没有generics所使用的接口所要求的函数,因此你不得不制作一个包装器类来容纳内建的类型。 
还有,一个generic的典型操作是以虚函数调用的方式实现的。与仅使用一个简单的内建操作(例如+或<)相比,这种做法可能代价高昂。Generics的实现方式表明它们不过是抽象类的语法糖而已。 
荣耀注:请注意,这并不是对Java/C# Generics的“全面”的描述,对C# 2.0 generics而言尤其不适合。
荣耀: 您说过,C++0x极有可能通过“一个机器模型外加支持线程的标准库设施”来支持并发访问,请解释一下您所指的机器模型是什么? 
Bjarne 当程序只有一个执行线程并且表达式都是按照程序员编写的顺序正确地执行时,处理内存是件很简单的事情:我们只管读写变量,无需想得太多。我们希望在今天更复杂的机器(具有高速缓存、宽数据总线以及多处理器)上仍然可以如此。一个内存模型是一套规则:它告诉编译器作者必须确保哪些东西在所有机器上都得到保证。 
考虑 
char x;
char y; 
如果某个多线程程序的一个线程在递增x,另一个线程在递增y,我们当然希望两个字符变量均被正确地递增。然而,在很多现代机器上,我们无法在寄存器和高速缓存之间或高速缓存和内存之间正好传递8个比特。因此,如果这两个字符都分配在同一个字(word)上,我们就无法做到在读写x时不去读写y。 
可能还存在更多的高速缓存(每一个都持有一份x和一份y的拷贝)。基本上,一个优秀的内存模型可以使程序员无需关心此类机器问题——这些问题留给编译器作者去操心,为程序员提供的总是显而易见的语言语义。 
荣耀: 您说过,C++0x的高级目标之一是使C++更易于教和学。通过“增强的一致性、更有力的保证”来做到这一点,您能否给出这方面的一些例子? 
Bjarne 比如说,目前的初始化规则太复杂且不规则。考虑一下在哪儿可以使用“={initializers}”、哪儿可以使用“(initializers)”、哪儿可以使用“=initializer”吧。我希望通过允许当前不被允许的用法,使得程序员只需学习和记住较少的规则,从而达到简化这个领域的目的。这方面的工作集中于实现“{initializers}”语法。 
荣耀注:举个例子。在现有的C++中,我们这样来“初始化”一个vector:
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
采用“{initializers}”语法,我们就可以消除对push_back()的重复调用。如下:
vector<int> v = { 1, 2, 3 };
一个“更有力的保证”的例子,如前所述,精确地定义一个内存模型从而使得并发程序更具有可移植性。 
我还希望看到针对所有标准容器的越界检查的标准方式。这种有益的保证的例子之一是:“如果你对任何标准容器的任何访问是越界的,那么就会抛出一个异常”。 
…… 
总而言之,关键的思想在于使得广泛的常用语句变得更易于学习和使用。 
荣耀: 顺带一问,您是否认为目前的泛型代码对于普通用户来说太难以理解了? 
Bjarne 是的。很多泛型代码和模板元编程代码太难以理解了,而且,对于哪怕很微小的用户错误给出的错误消息也非常难以理解。 
要想使得模板定义成为主流,我们必须使得模板更容易编写和使用。当前的很多用法过于聪明。优秀的代码应该是简单的(相对于现状而言),并且易于检查,易于优化(即高效)。 
荣耀: C++0x 会支持元数据和反射吗?此外,你对Boost.Serialization库又怎么看? 
Bjarne C++0x 不太可能支持元数据。我对Boost.Serialization库不予置评。 
荣耀: 为何C++0x无意支持元数据和反射?看上去它是不少C++程序员“梦寐以求”的特性。 
Bjarne 很多程序员梦寐以求很多语言特性,如果C++将它们统统采纳进来,C++就会因为过度膨胀而失去用处。元数据这样的语言特性需要付出较多的工作,已经超出了标准委员会的能力。而且我并不认为元数据很好地适合于C++对“静态类型以及在concepts和types之间的直接对应”的强调。元数据可以被用于隐藏“走访”数据结构(元数据)的例程中的程序的真实语义,我对此类用法的正确性和性能尚持怀疑态度。
荣耀: 在Unicode、XML以及网络方面,有没有C++0x标准库的好的候选者? 
Bjarne 关于Unicode的工作有在做,但主要是C风格的。除了线程和sockets外,尚无严肃的关于XML、网络方面的工作。虽然应该进行这方面的工作,但标准委员会好像没有足够的资源来做这些事。这儿的资源指的是“有时间、有兴趣的技术能手”。
荣耀: 一些人认为当前C++对TMP(Template Metaprogramming,模板元编程)的支持不够好,就像C对OOP的支持那样,仅仅勉强可行,您是否赞同这一点?为了更好地支持TMP,您认为还需要向C++0x中添加什么样的特性? 
Bjarne 就目前来看,C++对TMP的支持似乎比任何语言都要好。毋庸置疑,我们可以改进语言使其更好地支持TMP,问题是我们到底该不该这么做?什么样的做法才算是一种改善?什么样的程序最好执行于编译期?什么样的程序又应该采用通用语言特性进行实现?没错,我们是可以设计一种专用的语言,与C++相比它对TMP有着更好的支持,就像我们可以为任何特殊的任务设计一种专用的语言那样。问题还是那句话:这样做值得吗? 
在C++0x(标准库)的设计中,中心议题仍然是经典的泛型编程。 
荣耀: 一个关于TMP的小问题。C++98标准建议编译器实现至少要支持17层递归模板实例化,但由于这只是一个“建议”而非“规定”,一个即使只支持1层递归实例化的编译器也是遵从标准的,不是吗?换句话说,TMP的重要基础之一——递归模板实例化并未得到标准更为“名正言顺”的支持——这项技术是和特定编译器有关的。C++0x有意增强这一点吗? 
Bjarne 我不知道,不过17这个数字也太小了。 
荣耀注:实际上,我们常用的编译器对递归模板实例化的层数支持远大于17层,一般可达数百乃至数千层。
荣耀: 考虑到要添加现有C++98编译器所不支持的新语言特性,标准委员会在进行C++0x标准化工作的过程中,都采用哪些编译器来测试(验证)新语言特性?  
Bjarne 有很多编译器可以用来测试新语言特性。所有程序员都可以使用GCC进行尝试,学院人士可以使用EDG。所有编译器厂商都在他们的编译器中试验新语言特性。 
荣耀注:EDG(Edison Design Group)是一家专注于开发C/C++、Java以及Fortran 77编译器front end(用于将源代码翻译成某种中间语言,然后再由back end将中间语言翻译成机器码)的公司。Intel C++和Comeau C/C++等许多编译器都使用了EDG front end。
荣耀: 一个有点儿八卦的问题。C++标准委员会中有中国人吗?有中国人向C++标准委员会递交过提案吗? 
Bjarne 我想不起来最近的提案和中国人有关。委员会中有一个IBM的新代表,姓王。我猜他是中国人,但我还不认识他。 
考虑到中国有那么多人在从事计算机工作,我一直都很奇怪为什么看不到你们对C++0x标准化工作的参与。 
其他 
荣耀: 和运行期C++相比,编译期C++、尤其是模板元编程的优点何在?TMP最适合用于哪些场合?TMP已经成功地应用于哪些类型的应用中了? 
Bjarne 对于这一个问题我还没有确定的答案。我认为TMP主要适合于相对简单的编译期计算,即用于选择执行于运行期的代码,而不是用于获得数值结果的复杂的计算,例如计算阶乘或质数。在这样的场合下,TMP充当“高级的”#ifdef。 
荣耀注:“计算质数”意思是 求出小于给定正整数的所有质数。
荣耀: 是的。所以像Boost Type Trait这样的库已经被写进了Library TR。另外,我认为Blitz++这样的库对于科学计算来说还是非常有意义的。  
Bjarne 对。
荣耀: 一个基础的问题。为什么在TMP中不能使用浮点型数据?这儿存在什么技术上的困难吗?我没看到C++0x有支持编译期浮点型数据计算的迹象,您知道,这对于编译期数值计算来说还是很有意义的。 
Bjarne 浮点计算不是百分之百精确,我们不能依赖于它的计算结果进行比较……  
荣耀: 看上去在编译期C++和运行期C++之间存在着一道鸿沟,我们如何修补它?抑或如何将二者结合使用?  
Bjarne 问题仍然是:我们应该修补它吗?我们能从对TMP的显著改善中获得什么样的好处?  
荣耀: 您说过C++是一门“偏向于系统编程的通用语言”。“系统编程”是一个宽泛的概念,不同的人有着不同的理解,您的意思是? 
Bjarne 当我说系统编程时,我是指传统上与操作系统以及基础工具有关的编程任务。包括操作系统核心、设备驱动程序、系统工具、网络应用、编辑器、字处理工具、编译器、某些图形和GUI应用,以及数据库系统等。这类工作在当前的C++用户中占有主导地位(参见我的个人主页上的“Applications”单元)。 
荣耀: 尽管我们都知道在C++中面向对象和泛型编程同等重要,但是,您是否和大多数人一样,认为C++对泛型编程的强力支持是它与其他语言的显著区别? 
Bjarne 对泛型编程的强力支持只是显著的区别和显著的优势之一,即便与那些提供了“generics”的语言相比也是如此。一如既往,C++的主要力量不在于在某一方面表现完美,而是擅长于很多事情,因此,对于许多应用领域来说,C++都是一个优秀的工具。 
荣耀: 看上去C++程序的编译速度要比Java或C#的来得慢,原因何在?当然了,我们可以想象语言自身的复杂性以及模板的实例化(即使程序员没有显式地使用模板,但他所使用的库比如STL,也使用了模板)等因素使然。  
Bjarne Java 和C#这样较新的语言,其语言结构较为简单。更重要的是,它们不像C++这样在编译期做很多事情。形形色色的编译期评估是造成慢的一个主要原因,不过编译时间被拉长带来的好处往往是程序运行期性能的提高。还有一个原因值得一提,连接时间往往也被认为是构成C++编译时间一个不小的组成部分。 
荣耀: 多年以来——今天仍然如此,有很多人在讨论什么是“高级C++”,您能谈谈什么是高级C++吗?C++/高级C++的明天是什么样子? 
Bjarne 在我看来,人们在为了变得更聪明、更高级方面用力过度了。设计和编程的真正目标在于产生能够完成工作的最简单的解决方案,并且对该解决方案的表达要尽可能的清晰。理想的境界不是让那些看到你代码的人惊呼“哇塞,好聪明耶!”,而是“哈哈,这么简单?” 
我认为这样的简单代码将会包含大量的相对简单的泛型编程,同时带有一些类继承层次结构,后者为需要运行期解决方案的领域提供服务。用今天的话来说,此即为“多范型编程(multi-paradigm programming)”,但我们无疑需要为之寻找一个更好的术语。 
参考资源 
1.      The C++ Standards WG, http://www.open-std.org/jtc1/sc22/wg21/
2.      Bjarne Stroustrup, The Design and Evolution of C++, Addison-Wesley
3.      Bjarne Stroustrup, The Design of C++0x, C/C++ Users Journal (May 2005)
4.      Bjarne Stroustrup homepage, http://www.research.att.com/~bs 
 
荣耀
2005
年9月
南京师范大学

www.royaloo.com
 

你可能感兴趣的:(C++0x热点问题访谈)