|
【技术评论】
|
阅读 5033 次
|
[转贴]C++的未来之路:C++0x概览
2006-05-15 12:06:58
荣耀/李建忠 译
日期 : 2006年05月08日
C++0x的工作已经进入了一个决定性的阶段。ISO C++委员会对C++0x的目标是使其成为“C++09”。这意味着我们要在2008年完成这个标准以便被ISO成员国批准。最后提交的标准设施将选自目前正被讨论的那些提案。为了按时完成此项工作,委员会已经停止审查新的提案并将精力集中于目前已经被讨论的那些提案上。
本文简要描述了C++0x标准化工作的指导原则,展示了一些可能的语言扩展的例子,并列出了一些被提议的新标准库设施。
指导原则
C++是一门偏向于系统编程的通用编程语言。它
·是一个更好的C
·支持数据抽象
·支持面向对象编程
·支持泛型编程
当我说“系统编程”时,我是指传统上与操作系统以及基础工具有关的那一类编程任务。包括操作系统核心、设备驱动程序、系统工具、网络应用、字处理工具、编译器、某些图形和GUI应用、数据库系统、游戏引擎、CAD/CAM、电信系统,等等。这类工作在当前的C++用户中占有主导地位。例子参见我的个人主页“Applications”单元(http://www.research.att.com/~bs/applications.html)。
C++0x的目标是使以上的说法仍然成立。它并不是要消除这些编程风格(styles)(或“paradigms”,范型)之一(比方说,使C++不那么兼容于C),或者添加一种全新的“范型”。最有效的编程风格是联合使用这些技术,这也就是我们常说的“多范型编程(multi-paradigm programming)”。因此,我们可以说我们希望改进C++使其成为一门更好的多范型编程语言。
C++0x的高级目标是:
使C++成为一门更好的系统编程语言和构建库的语言。
- 而不是为特定子社群提供专用设施(例如数值计算或Windows风格的应用程序开发)。
使C++更易于教和学。
- 通过增强的一致性、更强的保证以及针对新手的设施支持。
换句话说,在C++98已经很强的领域(以及一些更多的、C++98支持的较为自然的、一般化的领域),C++0x应该比C++98做得更好。对于一些专有的应用程序领域来说,例如数值计算、Windows风格的应用程序开发、嵌入式系统编程,C++0x应该依赖于程序库。C++在基本语言特性(如基于栈的对象和指针)方面所具有的效率,和在抽象机制 (如类和模板) 方面所具有的通用性和灵活性,使得程序库在非常广泛的应用领域都能保持它的吸引力,也因此降低了C++对各种新的语言特性的需求。
我们不能为了降低C++在教与学方面的难度,而去移除某些语言特性。保持C++稳定性与兼容性是我们主要的考虑。因此,不管是以什么方式来移除其中任何重要的特性都是行不通的(而移除其中不重要的特性对于解决问题又没有实质性的帮助)。那么留给我们的选择恐怕只有“将规则一般化”和“添加更易于使用的特性”。两者都是我们的目标,但是后者更容易一些。例如,更好的程序库(容器与算法)可以帮助用户避免一些底层设施(例如数组与指针)带来的问题。那些能够 “简化程序库的定义和应用”的语言设施(例如“concepts”与“通用初始化器列表”,下面将会谈到它们)也将有助于改善C++0x的易用性。
一些人可能对此持有反对意见,“不要为了新手而将C++降格,适合新手的语言已经有很多了!”,或者“最好的办法还是将新手变成专家!”这些人的观点并非毫无道理,但是现实是新手总比专家要多。而且许多C++用户完全不必、也没有意愿成为C++专家——他们是各自领域的专家(比如物理学家、图形学专家、硬件工程师),只不过他们需要使用C++。在我个人来看,C++已经太过“专家友好”了,我们完全可以在花费很少的情况下为“新手们”提供更好的支持。事实上,这种支持不会损及任何C++代码的性能(零成本原则依旧适用)、灵活性(我们不打算禁止任何东西)、与简洁度。相反,我们的目标是简化这些理念的表达。最后,值得指出的是,C++是如此之大,而且应用如此广泛,各种设计技巧可谓汗牛充栋,以至于我们很多时候也都是“新手”。
C++0x的改进应该以这样的方式进行:结果所得语言应该更易于学和用。以下是委员会考虑的一些规则:
·提供稳定性和兼容性(针对C++98而言,可能的话还有C)
·优先考虑库设施,其次才是语言扩展
·只进行可以改变人们思考方式的修改
·优先考虑一般性而非专用性
·同时为专家和新手提供支持
·增强类型安全性(通过为当前不安全的设施提供安全的替代品)
·改进直接处理硬件的性能和能力
·适应现实世界
当然,对这些思想和规则的应用与其说是一门科学不如说是一门艺术,人们对于什么才是C++的自然发展以及什么才是一种新的范型有着不同的意见。C++ 0x将极有可能支持可选的垃圾收集机制,并将以一个机器模型外加支持线程的标准库设施(可能还有别的)来支持并发编程。一些人也许认为这过于激进,但我并不这么认为:人们已经在C++中(在垃圾收集有意义的领域)使用垃圾收集很多年了,而且几乎每一个人都曾使用过线程。在这些情况下,我们需要做的不过是将现行的实践加以标准化而已。
我们之所以专注于“只进行可以改变人们思考方式的修改”,是因为这种方式可以使我们的努力获得最大的回报。每一项改变都有代价,不管是在实现方面、还是在学习等其他方面。而且,每项改变的代价并不总是直接和其带来的回报正相关。语言上的主要进步/收益并非体现在如何改进程序员编写的某一行代码上,而是体现在如何改进程序员解决问题和组织程序的方式上。面向对象程序设计和泛型程序设计改变了很多人的思考方式 ——这也是C++语言设施支持这些风格的目的。因此,作为语言和程序库的设计者来说,最好的做法就是把我们的时间投入到那些能够帮助人们改变思考方式的设施和技巧上。
请注意最后一条规则“适应现实世界”。一如既往,C++的目标不是创建一门“最美丽”的语言(尽管只要有可能我们都希望“美丽”),而是提供最有用的语言。这就意味着兼容性、性能、易于学习,以及与其他系统和语言的互操作性,才是应该严肃考虑的问题。
语言特性
让我们来看看使用C++0x新特性的代码的可能模样:
template using Vec = vector>;
Vec v = { 2.3, 1.2, 6.7, 4.5 };
sort(v);
for(auto p = v.begin(); p!=v.end(); ++p)
cout << *p << endl;
在C++98中,除了最后一行代码外其余每一行都是不合法的,而且在C++98中我们不得不编写更多(易犯错误)的代码来完成工作。我希望无需我的解释你就可以猜测到这段代码的含义,不过我们还是逐行看一看。
template using Vec = vector>;
在这里,我们定义Vec作为vector>的别名。换句话说,我们定义一个名为Vec的标准vector,其工作方式正如我们常用的vector那样,除了它使用我自己定义的配置器(My_alloc)而不是默认的配置器之外。C ++中缺乏定义这种别名以及绑定(bind)部分而非全部模板参数的能力。按照传统,这被称为“template typedefs”,因为我们一般采用typedef来定义别名,但出于技术上的原因,我们偏向于使用using。这种语法的优势之一是,它将被定义的名字展示于易被人们发现的显著位置。还要注意另一个细节,我没有像下面这样写:
template using Vec = vector< T,My_alloc >;
我们将不再需要在表示结束符的两个“>”之间添加空格。原则上这两个扩展已经被接受了。
接下来我们定义和初始化一个Vec:
Vec v = { 2.3, 1.2, 6.7, 4.5 };
采用一个初始化列表来初始化用户自定义容器(vector>)是一种新方式。在C++98中,我们只能将这种初始化列表语法用于聚合体(包括数组和传统的struct)。至于究竟如何实现这种语言扩展仍然在讨论中。最可能的解决方案是引入一种新型构造函数:“序列构造函数”。允许上面的例子可以运作将意味着C++更好地满足其基础设计准则之一:对用户自定义类型和内建类型的支持一样好。在C++98中,数组比vector具有记号上的优势。在C++0x中,情况将不再如此。
接下来,我们对该vector进行排序:
sort(v);
为了在STL的框架内做这件事,我们必须针对容器和迭代器对sort进行重载。例如:
// 使用 < 对容器排序
template
void sort(C& c);
// 使用 Cmp 对容器排序
template
where Can_call_with
void sort(C& c, Cmp less);
// 使用 < 对序列排序
template
void sort(Ran first, Ran last);
// 使用 Cmp 对序列排序
template
where Can_call_with
void sort(Ran first, Ran last, Cmp less);
这里演示了C++0x目前提议中最具意义的扩展部分(也是有可能被接受的部分):concepts。基本上,一个concept就是一个type的 type,它指定了一个type所要求的属性。在这个例子中,concept Container用于指定前两个版本的sort需要一个满足标准库容器要求的实参,where子句用于指定模板实参之间所要求的关系:即判断式(predicate)可以被应用在容器的元素类型上。有了concepts,我们就可以提供比目前好得多的错误消息,并区分带有相同数目实参的模板,例如:
sort(v, Case_insensitive_less()); // 容器与判断式
和
sort(v.begin(), v.end()); // 两个随机访问迭代器
在concepts的设计中存在的最大的困难是维持模板的灵活性,因此我们不要求模板实参适合于类层次结构或要求所有操作都能够通过虚函数进行访问(就象Java和C#的泛型所做的那样)。在这些语言的泛型中,实参的类型必须是派生自泛型定义中指定的接口(在C++中类似于接口的是抽象类)。这意味着所有的泛型实参都必须适合于某个类层次结构。这将要求部分开发人员在设计的时候就做一些不合理的预设,从而为他们强加一些不必要的约束。例如,如果你编写了一个泛型类,而我又定义了一个类,只有在我知道你指定的接口、并将我的类从该接口派生的情况下,人们才可以将我的类用作这个泛型类的实参。这种限制太过严格。
当然对于这种问题总有解决办法,但那会使代码变得复杂化。另一个问题是我们不能直接在泛型中使用内建类型。因为内建类型(例如 int)并不是类,也就没有泛型中指定接口所要求的函数——这时候你必须为这些内建类型做一个包装器类,然后通过指针来间接地访问它们。另外,在泛型上的典型操作会被实现为一个虚函数调用。那样的代价可能相当高(相对于仅仅使用简单的内建操作来说,比如+或者<)。以这种方式来实现的泛型,只不过是抽象类的“语法糖”。
有了concepts之后,模板将保持它们的灵活性和性能。在委员会可以接受一个具体的concept设计之前,仍然有很多工作要做。然而,由于承诺显著更好的类型检查、更好的错误信息和更好的表达力,concepts将成为一个极有可能的扩展。它将使得我们从目前的标准容器、迭代器、和算法开始就能设计出更好的程序库接口。 最后,考虑最后一行用于输出我们的vector元素的代码:
for (auto p = v.begin(); p!=v.end(); ++p)
cout << *p << endl;
这儿与C++98的区别在于我们不需要提及迭代器的类型:auto的含义是“从初始化器(initializer)中推导出所声明的变量的类型”。这种对auto的使用方式可以大大消除当前替代方式所导致的冗长和易出错的代码,例如:
for (vector< double, My_alloc >::const_iterator
p = v.begin(); p!=v.end(); ++p)
cout << *p << endl;
这儿提到的新的语言特性的目标都在于简化泛型编程,原因在于泛型编程已经是如此流行,“使得现有语言设施受到了很大的压力”。许多“modern”的泛型编程技术接近于“write only”技术,并有孤立于其用户的危险。为了使得泛型编程成为主流(就象面向对象编程成为主流那样),我们必须使模板代码更易于阅读、编写、和使用。许多目前的用法只管编写时候的好处。但真正好的代码应该简洁(相对于它要做的事情来说)、易于检查、和易于优化(也就是高效)。这就意味着许多简单的思想可以在C++0x中简单地进行表达,并且结果代码坚定不移得高效。在C++98中前者的情况可不是这样,至少对于非常大范围的依赖于模板的技术的情况不是如此。借助于更好的类型检查和类型信息更广泛的使用,C++代码将会变得更简短、清晰、易于维护,也更容易获得正确性。
▲评论
› 网友 匿名 () 于 2006-07-09 00:13:47 发表评论说:
fff