`
nathan09
  • 浏览: 144067 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
文章分类
社区版块
存档分类
最新评论

关于c++0x

 
阅读更多
C++的未来之路:C++0x概览
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<class T> using Vec = vector<T,My_alloc<T>>;
Vec<double> 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<class T> using Vec = vector<T,My_alloc<T>>;

   在这里,我们定义Vec<T>作为vector<T,My_alloc<T>>的别名。换句话说,我们定义一个名 为Vec的标准vector,其工作方式正如我们常用的vector那样,除了它使用我自己定义的配置器(My_alloc)而不是默认的配置器之外。C ++中缺乏定义这种别名以及绑定(bind)部分而非全部模板参数的能力。按照传统,这被称为“template typedefs”,因为我们一般采用typedef来定义别名,但出于技术上的原因,我们偏向于使用using。这种语法的优势之一是,它将被定义的名 字展示于易被人们发现的显著位置。还要注意另一个细节,我没有像下面这样写:

template<class T> using Vec = vector< T,My_alloc<T> >;

  我们将不再需要在表示结束符的两个“>”之间添加空格。原则上这两个扩展已经被接受了。

  接下来我们定义和初始化一个Vec:

Vec<double> v = { 2.3, 1.2, 6.7, 4.5 };

   采用一个初始化列表来初始化用户自定义容器(vector<double,My_allocator<double>>)是一 种新方式。在C++98中,我们只能将这种初始化列表语法用于聚合体(包括数组和传统的struct)。至于究竟如何实现这种语言扩展仍然在讨论中。最可 能的解决方案是引入一种新型构造函数:“序列构造函数”。允许上面的例子可以运作将意味着C++更好地满足其基础设计准则之一:对用户自定义类型和内建类型的支持一样好。在C++98中,数组比vector具有记号上的优势。在C++0x中,情况将不再如此。

  接下来,我们对该vector进行排序:

sort(v);

  为了在STL的框架内做这件事,我们必须针对容器和迭代器对sort进行重载。例如:

// 使用 < 对容器排序
template<Container C>
void sort(C& c);

// 使用 Cmp 对容器排序
template<Container C, Predicate Cmp>
where Can_call_with<Cmp,typename C::value_type>
void sort(C& c, Cmp less);

// 使用 < 对序列排序
template<Random_Access_iterator Ran>
void sort(Ran first, Ran last);

// 使用 Cmp 对序列排序
template<Random_access_iterator Ran, Predicate Cmp>
where Can_call_with<Cmp,typename Ran::value_type>
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的设计中存在的最大的困难是维持模板的灵活性,因此我们不要求模板实参适合于类层次结构或要求所有操作都能够通过虚函数进行访问(就象JavaC#的 泛型所做的那样)。在这些语言的泛型中,实参的类型必须是派生自泛型定义中指定的接口(在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<double> >::const_iterator
p = v.begin(); p!=v.end(); ++p)
cout << *p << endl;

   这儿提到的新的语言特性的目标都在于简化泛型编程,原因在于泛型编程已经是如此流行,“使得现有语言设施受到了很大的压力”。许多“modern”的泛 型编程技术接近于“write only”技术,并有孤立于其用户的危险。为了使得泛型编程成为主流(就象面向对象编程成为主流那样),我们必须使模板代码更易于阅读、编写、和使用。许 多目前的用法只管编写时候的好处。但真正好的代码应该简洁(相对于它要做的事情来说)、易于检查、和易于优化(也就是高效)。这就意味着许多简单的思想可 以在C++0x中简单地进行表达,并且结果代码坚定不移得高效。在C++98中前者的情况可不是这样,至少对于非常大范围的依赖于模板的技术的情况不是如 此。借助于更好的类型检查和类型信息更广泛的使用,C++代码将会变得更简短、清晰、易于维护,也更容易获得正确性。
库设施

  从理想上说,我们应该尽量不修改C++语 言,而集中于扩充标准库。然而,那些具有足够大的通用性的能够进入标准的库设计起来并不容易,而且一如既往,标准委员会缺乏足够的资源。我们由相对少的一 组志愿者构成,并且都有“日常工作”。这就给我们能对新库进行的冒险添加了不幸的限制。另一方面,委员会很早就开始库的工作了,一个关于库的技术报告 (Library TR)也在最近被投票通过了,它提供了一些对程序员来说具有直接的用处的设施:

  ·哈希表(Hash Tables)

  ·正则表达式(Regular Expressions)

  ·通用智能指针(General Purpose Smart Pointers)

  ·可扩展的随机数字设施(Extensible Random Number Facility)

  ·数学专用函数(Mathematical Special Functions)

  我尤其赏识能够有标准版本的正则表达式和哈希表(名为unordered_map)。此外,Library TR还为基于STL构建泛型库的人们提供了广泛的设施:

  ·多态函数对象包装器(Polymorphic Function Object Wrapper)

  ·Tuple类型

  ·Type Traits

  ·增强的成员指针适配器(Enhanced Member Pointer Adaptor)

  ·引用包装器(Reference Wrapper)

  ·用于计算函数对象返回类型的统一方法(Uniform Method for Computing Function Object Return Types)

  ·增强的绑定器(Enhanced Binder)

   这儿不是详述这些库的细节或者深入讨论委员会希望提供的更多的设施的场合。如果你对此感兴趣,我建议你看看WG21站点(参见后面的“信息资源”)上的 提案、库“期望列表(wish list)”(在我的主页上),以及BOOST库(http://www.boost.org/)。我个人希望看到更多的对应用程序构建者有着直接好处的 库,例如Beman Dawes的用于操纵文件和目录的库(当前是一个BOOST库)以及一个socket库。

  目前的提案列表仍然相 当的保守,并不是各个地方都如我所期望的那样进取。不过,还有更多来自于委员会海量的建议中的提案正被考虑,将有更多的库或者成为C++0x标准的一部 分、或者成为将来委员会的技术报告。不幸的是,资源的缺乏(时间、财力、技能、人力等)仍将继续限制我们在这个方向上的进展。悲哀的是,我无法给大家太希 望得到的一个新标准库——一个标准GUI库——带来希望。GUI库对于C++标准委员会的志愿者来说是一个太大的任务,而且是一个太困难的任务,因为已经 有很多(非标准、大型、有用、但受支持的)GUI库的存在。请注意,纵然它们是非标准的,主要的C++ GUI库还是有比大多数编程语言更多的用户,并且通常有更好的支持。

  除了这些通用的库之外,委员会还在“Performance TR”中提呈了一个到最基层的硬件的库接口。该TR的首要目标是帮助嵌入式系统程序员,同时还驳斥了有关C++代码性能低下以及C++正变得不适合低层任务的流言蜚语。

  总结

  “将一个数组中所有的形状绘制出来”是面向对象编程中一个经典的例子(回想早期Simula的日子)。使用泛型编程,我们可以将其泛化,从而支持绘制任意容器(存储着Shape指针)中的每个元素。

template<Container C>
void draw_all(C& c)
where Usable_as<typename C::value_type,Shape*>
{
for_each(c, mem_fun(&Shape::draw));
}

   在C++0x中,我们希望将Container作为一个标准concept,将Usealbe_as作为一个标准判断式。其中for_each算法已经 在C++98中有了,但是接受容器(而非一对迭代器)作为参数的版本要依赖于C++0x中的concept。其中的where子句用于支持算法来表达其对 于实参的要求。就这里来说,draw_all()函数(明确)要求容器中的元素必须可以被作为(即可以隐式转换为)Shape*使用。这里的where子 句通过简单要求一个Shape*容器,为我们提供了某种程度的灵活性和通用性。除了元素为Shape*的任何容器外,我们还可以使用那些元素可以被用作 Shape*的任何容器,例如list<shared_ptr<Shape*>>(其中shared_ptr将有可能成为C++ 0x标准库中的一个类)、或者元素类型继承自Shape*的容器,例如deque<Circle*>。

  假设我们有p1、p2、p3三个点,我们可以编写如下代码来测试draw_all():

vector<Shape*> v = {
new Circle(p1,20),
new Triangle(p1,p2,p3),
new Rectangle(p3,30,20)
};

draw_all(v);

list<shared_ptr<Shape*>> v2 = {
new Circle(p1,20),
new Triangle(p1,p2,p3),
new Rectangle(p3,30,20)
};

draw_all(v2);

   “绘制所有形状”的例子很重要,因为如果你可以很好地实现它,那么你就掌握了大多数面向对象编程中关键的东西。通过融合泛型编程(concepts与模 板)、常规编程(例如独立标准库函数mem_fun())、和简单数据抽象(mem_fun()函数返回的函数对象),上面的代码演示了多范型编程的力 量。这个简单的示例为我们开启了一扇通往许多优雅和高效的编程技巧的大门。

  我希望在看完上面的例子之后,你的反应是“如此简单!”, 而不是“如此聪明!如此高级!”在我看来,许多人都在聪明和高级的道路上太过投入。但设计与编程的真正目的是使用最简单的方案来完成工作,并用尽可能清晰 的方式来表达。C++0x设计的目标便是更好地支持这样的简单方案。


[转] C++0x 热点问题访谈

C++0x热点问题访谈

2004年底前后,经过较长一段时间的沉默,一批世界级的C++著作相继面世。20054月,在挪威Lillehammer举行的C++标准委员会会议上,Bjarne Stroustrup促成委员会达成一致意见:让C++0x中的x等于9200511月,Bjarne StroustrupHerb SutterStanley B. LippmanAndrei Alexandrescu等前辈、新锐将在Las Vegas庆祝C++ 廿周年。2005年底,C++中国社群将在上海举办首届“现代C++设计和编程”技术大会……C++好戏连台,令人振奋。笔者近日就C++0x以及其他一些热点问题请教了Bjarne先生。大师观点,不敢专美,整理成文,以飨同好。

C++0x

荣耀:Library TR1library Technical Reports,库技术报告)和TR2的动机是什么?TR1TR2TR1/TR2C++0xPerformance TRPerformance Technical Reports,性能技术报告)和C++0xBoostC++0x之间的关系如何?

BjarneLibrary TR是对标准库改进工作的具体结果的体现。当一套设施、特性就绪后,TR1即被表决通过,然后人们继续向TR2前进。TR1TR2之间的区别仅在于“时间不同,所做的事情不同”而已。大部分TR1TR2中的内容有望成为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&EBjarne写的一本关于C++语言设计原理、设计决策和设计哲学的专著。该书全名是《The Design and Evolution of C++》。零开销原则的含义是:无需为未使用的东西付出代价。详情参见D&E

荣耀:您最近提到在C++0x标准化过程中,委员会奉行的若干原则中有这样一条:只做可以改变人们思考方式的改变,请问此原则的出发点是什么?

荣耀注:D&E中并没有明确地提及这条设计原则,但Bjarne在最近的一篇文章里特别提到了这一点,故有此一问。

Bjarne任何改变都会带来在实现、学习等方面的代价。对一个程序员编写具体哪一行代码的方式的改进不会带来太大的好处,能够改进程序员解决问题和组织程序的方式才可以。面向对象和泛型编程已经改变了很多人的思考方式,这也是很多C++语言设施支持这些风格的用意。因此,对于语言和库设计者来说,最好将时间花在有助于改变人们思考方式的设施和技术之上。

荣耀:如果让您分别列出可能会进入C++0x标准的Top 5语言新特性和库扩展,您愿意列出哪些?

Bjarne语言特性包括:

² Concepts

² 一般化的初始化(generalized initialization

² 对泛型编程的更好的支持(autodecltype等)

² 内存模型

² ……

荣耀注:Concepts在泛型编程中具有极其重要的地位,但C++98并未对concepts提供直接的支持,它们只是以文档的形式连同一套松散的编程约定而存在。目前STL中的concept的作用仅限于在模板实例化时对模板参数进行检查,然而,如果某个函数模板参数不符合某个concept,编译器并不能对该函数模板的定义进行及早的强类型纠错。尽管Bjarne先生一向对语言扩展持保守态度,但在为concept提供语言层面的直接支持方面却一向积极,实际上,他本人正是为C++加入concept的最早的提案人之一。几乎可以肯定该语言特性会被加入到C++0x之中。

荣耀注:关于“一般化的初始化”问题可参见后面的问答。内存模型也就是后面所说的机器模型。autodecltype则分别是计划加入C++0x的新关键字和操作符,它们均可用于改善泛型编程。

库设施则有:

² unordered_maps(即哈希表)

² 正则表达式匹配

² 线程

² ……

我不愿意勉强向这种“top N”列表中添加更多的东西,关于还有哪些主要的东西应该被添加进来,目前尚存在激烈的讨论。

荣耀:您说过,“在C++0xconcept的设计中存在的最大的困难是维持模板的灵活性。我们不要求模板参数适合于类层次结构或要求所有操作都能够通过虚函数进行访问(就象JavaC#的“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库又怎么看?

BjarneC++0x不太可能支持元数据。我对Boost.Serialization库不予置评。

荣耀:为何C++0x无意支持元数据和反射?看上去它是不少C++程序员“梦寐以求”的特性。

Bjarne很多程序员梦寐以求很多语言特性,如果C++将它们统统采纳进来,C++就会因为过度膨胀而失去用处。元数据这样的语言特性需要付出较多的工作,已经超出了标准委员会的能力。而且我并不认为元数据很好地适合于C++对“静态类型以及在conceptstypes之间的直接对应”的强调。元数据可以被用于隐藏“走访”数据结构(元数据)的例程中的程序的真实语义,我对此类用法的正确性和性能尚持怀疑态度。

荣耀:UnicodeXML以及网络方面,有没有C++0x标准库的好的候选者?

Bjarne关于Unicode的工作有在做,但主要是C风格的。除了线程和sockets外,尚无严肃的关于XML、网络方面的工作。虽然应该进行这方面的工作,但标准委员会好像没有足够的资源来做这些事。这儿的资源指的是“有时间、有兴趣的技术能手”。

荣耀:一些人认为当前C++TMPTemplate Metaprogramming,模板元编程)的支持不够好,就像COOP的支持那样,仅仅勉强可行,您是否赞同这一点?为了更好地支持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。所有编译器厂商都在他们的编译器中试验新语言特性。

荣耀注:EDGEdison 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++程序的编译速度要比JavaC#的来得慢,原因何在?当然了,我们可以想象语言自身的复杂性以及模板的实例化(即使程序员没有显式地使用模板,但他所使用的库比如STL,也使用了模板)等因素使然。

BjarneJavaC#这样较新的语言,其语言结构较为简单。更重要的是,它们不像C++这样在编译期做很多事情。形形色色的编译期评估是造成慢的一个主要原因,不过编译时间被拉长带来的好处往往是程序运行期性能的提高。还有一个原因值得一提,连接时间往往也被认为是构成C++编译时间一个不小的组成部分。

荣耀:多年以来——今天仍然如此,有很多人在讨论什么是“高级C++”,您能谈谈什么是高级C++吗?C++/高级C++的明天是什么样子?

Bjarne在我看来,人们在为了变得更聪明、更高级方面用力过度了。设计和编程的真正目标在于产生能够完成工作的最简单的解决方案,并且对该解决方案的表达要尽可能的清晰。理想的境界不是让那些看到你代码的人惊呼“哇塞,好聪明耶!”,而是“哈哈,这么简单?”

我认为这样的简单代码将会包含大量的相对简单的泛型编程,同时带有一些类继承层次结构,后者为需要运行期解决方案的领域提供服务。用今天的话来说,此即为“多范型编程(multi-paradigm programming)”,但我们无疑需要为之寻找一个更好的术语。



[转]C++0x设计之路——把握现在 展望未来

[著] Bjarne Stroustrup [译]firingme

[译者注:原文地址:http://www.research.att.com/~bs/rules.pdf]

每 隔一两天,我都会收到关于改进C++的e-mail建议。很多建议如果真的能够成为语言或者标准库的一部分将是一件不错的事情,会让很多程序员深受其益。 当然,综合来看,这些建议有很多重复之处,因此,也可以说,在我提出的列表中仅有上百个建议而已——具体的数量取决于你对“相关建议”这个概念的定义。人 们希望改进的语言特性的一份不完全列表在 http://www.research.att.com/~bs/evol-issues.html;而与之对应的要求标准库改进的不完全列表则在 http://lafstern.org/matt/wishlist.html。我对这两份列表的基本观点是:前者太长而后者太激进。

现在,假设你就是ISO C++标准委员会负责人,有权决定下一代C++标准的内容,请你仔细思考:什么是可以加入的?什么是应该剔除的?什么是需要改变的?这不是开玩笑,请暂时 中断阅读,严肃认真地权衡把握,然后作出一份改进列表。如果你在读完这篇文章之后仍然对你的结论感到满意,请把它们加上注释并且e-mail给我。

在修订标准的过程中,有一个问题必须首先明确:我们希望取得什么样的结果?当然,这个问题很难有完美的答案;我们甚至不能准确地定义“我们”这个词的范围 到底有多大!C++社群是一个庞大的集体——去年IDC的统计结果是300万人左右——而且这个集体内部还可以细分为很多更小的单位。我们使用现有的几乎 全部的计算机和操作系统,与所有的应用软件协同工作,生活在世界上全部的国家。这种情况必然导致我们具有各自完全不同的需求和愿望,而对我而言,这就表示 我必须特别小心并且遵循一些基本的原则,以避免在迎合一部分人需要的同时损害了另外一部分人的利益。

在本文中,我将阐释在往C++0x——下一代C++标准——发展的过程中,我们所必须遵循的一些最主要的原则。很明显,这些原则都是[3]中的“概要原 则”的具体发展和体现。这种情况并不是偶然的。我的目的是进一步加强C++中已经被证明了的最具威力的部分,而不是试图去发明一项新的标准进而带来一场翻 天覆地的大变化。这就是说,我的目标并不是让标准C++继续成为一门充满活力的语言,C++应该成长,应该适应,应该在我们整个社群现在和将来所面临的各 种问题前,成为一件更有效率的工具。在这里,请注意我将行文语气转换成了第一人称,我想强调的是,我只不过是一个拥有很多成员的标准委员会中的一员。C+ +社群提出了大量的覆盖很多领域的观点,尽管我所持的观点是由大部分成员的认同所支撑的,可是这里给出的原则及其在现实世界中的应用仍然需要进一步的讨 论、阐释和试验。这也是对待它们本应有的态度。

那么,为什么我们不干脆接受所有合情合理的建议,让大家皆大欢喜呢?因为现在已经有了太多的建议等待我们去分析并给出详细而充分的说明。如果我们接受所有 的建议的话,那完成这项工作将需要6到7年,而不是2,3年而已;并且将由此产生大量的新特性需要程序员们重新学习,而大部分程序员都将会偏安于一个较小 而自我感觉舒适的子集之内,对大部分新特性视而不见;而且这样做的结果将会对“零开销”原则提出严峻的挑战——众多的特性将要求实现者耗费巨大的努力去实 现新的标准,而在实现各个不同的特性时又会产生交互影响,还有很多建议的提出根本没有考虑效率因素,并需要一整套复杂而精致的运行时机制对其提供支持。不 仅如此,上述一切还是建立在各编译器提供商愿意实现这个新标准的基础上——而我对他们是否愿意还完全没有把握。所以,在你下次想要提出“仅加上我这两个新 特性吧”这样的要求前,请仔细考虑上面这些利害关系,三思而后言。

设计和演化

C++0x将会100%兼容现有的C++标准。如果你现有的代码符合C++98标准,那么在过渡到C++0x的过程中,它们将有非常好的可移植性。对这个 最好的保证就是标准委员会需要对比你手头更多的旧代码负责。尽管如此,我们也并未固步自封,我们明白C++的进一步进化是必要的,而且我们都希望未来的代 码能够更简单、更优雅、更易维护,甚至——更高效。

我们的目的是兼容,可我们也认识到,在某些情况下,付出微小的不兼容的代价的确能换来巨大的进步。一个明显的例子就是加入一个新的关键字。我们试图避免不 兼容,可绝对的“没有不兼容”就意味着“没有改变”或者让新特性都具有丑陋的语法形式。就我个人而言,我极度不喜欢一些折衷、将就的做法。例如:利用宏提 供一个__XXX关键字:

#define XXX __XXX

而不是直接加入一个新的XXX关键字。这种折衷是为 那些持有包含XXX的代码而又无法对其进行修改的人提供的,而这个折衷确是以社群中其他所有人的不便为代价的,并且它给学习者增加了复杂度,并且给那些不 太喜欢C++的人又提供了一个廉价的谈资。当然,前面所说的那种情况的确是存在的,可你仍然有别的选择——不升级或者通过一个编译器开关获得向后兼容性。

通 常会认为,语言的演化和语言的设计是两件相去甚远的事情。而我想指出这种观点是错误的,语言的演化就等同于语言的设计。只不过,语言的演化和通常想象中的 “白手起家”式的语言设计并不相同,它既要受到现存的大量代码的约束,同时也可以从一些将现有经验直接应用的过程中(通过反馈)受益良多。保证新标准的兼 容性是一件非常困难的工作,可我们能籍此使得数量巨大的社群成员轻松地过渡到新标准,并能使得新旧特性之间能够互动交流,平滑合作。选择这种设计式的演化 方式,我们也能避免由于我们自身的经验和眼界所限而扼杀一些非常有用的程序设计技术的情况的出现。

如果沉溺于一些微不足道的形式符号表现 的问题的话,那语言的设计工作就将变得杂乱无章,毫无条理。一门语言中,最明显最重要的特性就是那些能给其用户提供一种新的更有效率的编程风格的特性。就 C++而言,这就意味着语言本身和标准库的变化必须能将一些崭新的编程和设计技术带入主流群体(包括工业界和教育界)的使用之中。只有那些能改变我们思维 方式的特性才是值得提供的。

总结:C++0x的目标是在高度可移植的强力限制下做出的一种演化。这种演化应该能在现实世界中带来巨大的改进。

一般和特殊

C++对于常规特性(主要就是类)的突出强调是它的主要威力。

对一些特定的功能的要求和对一些符号表达进行改进是非常常见的改革建议。可就算这些建议真地能够成为标准,也并不会给大家带来多少快乐。毕竟,如果一项特 性只不过是针对一个特定问题的直截了当的解决方案的话,那么它通常和系统的其他部分并没有多大联系,它会非常容易解释,实现起来也不会有太大困难,并且针 对一些精心挑选的代码,它可以有逻辑上最小的表现形式。那些喜欢拿语言做比较的人就经常使用一些这样子的功能清单。这种观点的问题是,我们所需要面对的问 题在本质上是无穷尽的,这就要求我们提供一个无穷尽的功能集合。Pascal中的过程参数和C#中的委托机制就是一个明显的例子。与之不同的是,C++ (在之前是K&R C)的传统做法一般是提供一些通用的特性集合,让优秀的程序员能够对很大一部分的问题构造解决方案。指针和类的概念就是明证。

C++对于通用特性的的强调已经成为其最具威力的方面;而同时,由此带来的在某些特定功能(例如“属性”和线程)上的缺乏也已被认为是C++最薄弱的环 节。很显然,这两个观点都是正确的。不过,我们仍然要对那些能提供更有力的抽象手段的的通用特性给予强烈的关注;C++社群的多样性决定了这一点。如果要 为某些特定领域——例如Windows程序或者嵌入式系统程序——提供一些精心剪裁的特殊的功能,而这些功能又仅能为它们所特定的领域服务的话,那这些功 能将会是一个承重的负担。C++0x不会成为一门“Windows语言”,或者“web语言”,当然更不可能是“嵌入式语言”。它将是一门通过一些公用的 基础设施来支持所有这些领域——而且范围更大——的通用型语言。

对通用机制的偏爱,一个很重要的原因就是这种机制通常可以为那些现在还完全意料不到的问题提供解决方案,而特殊机制则做不到这一点。可以肯定的是,未来肯 定会有一些令人措手不及的变故发生。我可不希望一门语言只能表达那些在它的设计阶段就已经明确指定 的领域。

让C++变得更加通用的一个很明显的可改进点就是为范型编程提供更好的支持以及更灵活的初始化/构造机制(示例中有进一步解释)。另一个明显的可改进点就 是在并行、并发、分布式计算越来越普及的今天,为它们提供某种支持。种种不同的途径和技术表明了不存在一种单独的机制可以覆盖所有类型的应用。因此,一种 明显的做法就是提供一些很简单的语言机制并且与程序库(利用范型和面向对象技术构建)相配合。

总结:C++0x的目标是提供通用型的语言机制,这些机制可以自由组合,并针对某些特定的领域提供构建于这些通用机制之上的标准程序库。

专家和新手

C++有一种转变为“专家专用”的语言的趋势。而在专家群(个人组合或者是通过网络组合)中,对于如何帮助新手,则很难建立广泛的共识,有时甚至对此无人 问津。一个典型而普遍的观点(在这样的群体里)是:帮助新人最好的办法就是让他成为专家。可问题是,成为专家需要花费大量的时间,而大多数人在这个过程 中,还必须进行一些实际的工作,并被要求保持一定的工作效率。更有趣的是,很多C++的新手对于成为C++的专家毫无兴趣。如果你是一个物理学家,只是碰 巧需要做一些计算性的工作;或者是一名商务人员,只不过因工作需要要接触一些软件上的事务;甚或还是一个正在学习程序设计的学生,你只希望学习那些能让你 完成工作的语言设施。你并不想成为语言专家——你只希望在你自己的领域内成为专家,并且了解一些能让你完成工作的语言特性就可以了。在有合适的程序库的支 持下,C++可以做到这一点——而且它也的确被这样广泛使用。当然,这样做会有陷阱,会有缺陷,并且传统的教育手段给这种C++的“临时使用”增添了不必 要的困难度。而利用一些最新的工作成果,C++0x可以在这个上面做得更好。

请考虑一些极其平凡的例子。你曾经写过这样的或者类似的代码吗?

vector> v;

或者:


int i = extract_int(s); // s is a string, e.g. "12.37"

或者:

vector::iterator p = find(tbl.begin(),tbl.end(),x); // tbl is a const vector

如 果你从未写过类似的代码,那我想你要么是以另外一种同样有自己问题的方式来使用C++,要么就是你很少使用C++。允许>>符号作为两个模板 参数的终结符可以解决第一个问题;为string提供一个标准的数值解析操作可以让新人们免去查找形如extract_int()这样的函数定义之苦;而 第三个问题的解决方案,则是让p的类型能够由其初始化形式自动推导得出,就像下面这样:

auto p = find(tbl.begin(),tbl.end(),x);

// tbl is a const vector

// p becomes a vector::const_iterator

可以证明,上述的>>和auto这两个解决方案是符合C++0x的原则的。而为“任何程度的新手”提供帮助,则要求语言本身和标准库共同努力。并且对教育的关注将会是核心议题;相关讨论,可以参考[4]。

而根据concepts的重载(请参考示例部分),则可以得到一个更为简洁的的方案:

auto p = find(tbl,x);

// tbl is a const vector

// p becomes a vector::const_iterator

为帮助新人而加入的特性(包括所有层次的技术和意见)不应与语言其它部分隔离而治,自成一派,形成一些“新人卫星城”一类的东西。和所有的特性一样,他们应该能在主要的系统内得到应用,和其他特性平滑互动,并且为进一步学习完整的语言和标准库提供一条道路。

总结:C++0x必须为各种程度的初学者提供比现有的C++更好的支持——这包括减少易错的语言特性和提供更多的支持库。

类型安全

对于缺乏类型安全的问题,最直接的解决方案就是提供一个以静态类型为基础的带范围检查的标准库,并且在C++的教学中以其为基础。

优雅和高效的代码的秘诀就是建立在灵活和易扩展的类型系统之上的类型安全。C++0x不可能使得C++达到完全的类型安全——这样将会导致数组、未初始化 指针、联合、窄向转型、C风格的链接以及更多的功能都不可使用。这样做同样会让在很多嵌入式系统中所必需的对硬件的存取操作产生问题。那么,我们能怎么 做?类型安全的缺乏是很多易错的,效率不佳的问题的根源所在。例如:

void get_input(char* p)

{

char ch;

while (cin.get(ch) && !iswhite(ch)) *p++ = ch;

*p = 0;

}

这段代码就足以让人心生寒意,惶惶然不知如何是好。同样的,如果你对效率极其关注,下面这种通用的链表的实现方法相信也难以让你满意:

struct Link {

Link* link;

void* data;

};

void my_clear(Link* p, int sz) // clear data of size sz

{

for (Link* q = p; q!=0; q = q->link) memset(q->data,0,sz);

}

我们该怎么办?基本上,我们所能做的,就是为那些不安全的操作提供其他的可选途径,并且依靠工具的帮助来检测那些不安全的(通常也是“传统”的)用法。例如:

string s;

cin >> s;

这个替代品完美地解决了前面的那个低级而臃肿的get_input()所具有的问题;而对于第二个问题,既明了又高效(至少具有高效的潜力)的解决方案很干净利落:

template void my_stl_clear(In first, In last)

{

while (first!=last) *first++ = 0;

}

这 个可以作为my_clear()的一个完美的替代品。my_clear()的问题在哪?当然,从代码行数而言,它比my_stl_clear()更长一 些,不过在考虑效率问题的时候,这并不是关键所在。问题的关键在于它缺乏类型信息。这意味着编译器必须假设在往q->data写入时(也就是 memset中),*(q->link)的内容甚至q->link本身都有可能改变。这基本上就让优化器在此毫无用武之地。而一旦我们能够静 态地确定data和link是两种不同类型,编译器就可以认为这两者不会相互影响。所以,在有一个还可接受的优化器的情况下,my_stl_clear ()应该要比my_clear()快很多倍。另外,由于我们使用了void*类型,为了存取其内容,我们不得不使用一些折衷的办法,memset()的使 用就是一例:在这种情况下,我们最后经常被迫使用函数——甚至是间接调用的函数——来达到目的。而这样做,有时其开销是巨大的。

对于缺乏 类型安全的问题,最直接的解决方案就是提供一个以静态类型为基础的带范围检查的标准库,并且在C++的教学中以其为基础。当然,这样做也无法消灭全部的类 型错误——很多时候,程序员会选择采用“硬编码的原语”的风格来写程序,这样做的原因有很多,而在程序员决定要这样做的时候,他们则必须关闭检查。另外, 还存在另外的很多很多标准库尚未涵盖的领域。而这个问题的解决则要求标准库设立一个标准,要求其他的类库遵照此标准与其协同工作。

一个系统地采用范围检查的标准库必然要求系统地使用异常,这对于那些实时性要求不甚严格的程序而言并没有问题。而且现在我们也已经知道应该如何处理异常了(请参考[5]的附录E)。

总结:C++0x并不能完全填补现有C++的类型体系的漏洞,可它将不会引入新的漏洞,并且将会提供一些避免那些先天不足的设施的方法——主要是通过标准库提供一套类型安全(编译时或者运行时)的替代品。

效率和机器模型

通 过将语言特性简单映射为硬件功能获得高效率是C++成功的核心(C也一样)。有个明显的例外就是使用异常时C++将要求一个最小的运行时支持。RTTI (dynamic_cast和typeid)和自由存储(new和delete)仅在你直接或间接使用它们时才被包含进来。这种可以消灭潜在开销的能力在 某些应用领域——主要是嵌入式领域——是极其重要的。尽管近年来,技术上已经能越来越高效地处理异常问题,可在某些实时性要求极高的领域,其结果还是难以 预期。在必要的时候,可以通过编译器开关禁止异常的使用。

C++0x中没有任何事物将会改变现有的状况,C++以前是,并且C++0x将 会继续是那些追求最高效率以及最小资源消耗的应用的首选语言。如果C++0x中加入了一些需要运行时支持的特性,那这些特性也肯定会被设计成只有在代码中 真正使用了的时候,才会要求获得额外的支持。这就是,著名的“0开销”原则:“不使用,不负责”以及“媲美手写”,这条原则将仍然是C++最根本的信条。

C+ +的硬件模型非常简单:基本的类型直接映射为硬件能够识别的实体,例如:字节、字以及寄存器;对象间的联系直接映射为硬件间的联系;各种操作直接映射为机 器指令;任何操作都可以直接完成,不需要额外的分配、间接性以及不必要的七弯八绕。现在的挑战是如何将并发加入这幅图画中。

总结:C++0x将继续遵循“0开销”原则,并且继续保持能够直接而有效的使用所有硬件的机器模型。

语言和程序库

并不是每个库都必须进入标准。

一门语言不可能覆盖任何事物,可是一个巨大的程序库可以做到这一点。并不是每个库都必须进入标准。C++社群有充足的空间来容纳一个大的标准库和一个工业 库(商业的和开源的都可以)。标准委员会的程序库技术报告(Library TR[7])通过增加正则表达式和哈希表等基础设施的方式扩充了标准库的内容。Boost社群则对什么东西是可能成为标准的给出了另外的例子。然而,甚至 这种形式的扩充也无法满足我的胃口。我希望看到C++标准库成为一个系统程序设计的更具弹性的平台,这当然也应该包括对并发和地理上的分布式计算的支持。 “分布式”也许超出了社区能力的限制——毕竟我们也只不过是一群有自己的日常工作并且没有多余的经费支持开发的志愿者。尽管如此,我们能够,也必须,怀有 梦想。

“程序库”是一个社区能够承受得起的,能自由探索并有望收获幸运的领域。在这里,社区能够提供大量的(非常重要的)支持。一个程序库和一个语言特性的关键 性区别就在于它并不是独占式的:如果你不喜欢一个库,换一个就是。而且,程序库可以在和编译器提供商完全无关的情况下,完全自由地进行开发、测试甚至是部 署。C++为各程序库提供的非常富有表现力的机制——甚至在效率要求严格和资源严重受限领域——不应该被低估。现有的例子可以参看Boost [8]。C++0x应该能让建立程序库更有效率。

一个最常见而普遍的对C++的要求就是一个标准的GUI库。然而技术上、经济上还有政治上对其阻碍力量是巨大的。尽管如此,回顾过去,我们也曾遭遇过对在 C++98中融入一个优秀的容器和算法库的巨大阻碍。可结果STL出现了。我希望发生一个奇迹,梦想着能有一个标准的界面将现有的各种商业的和开源的 GUI设施标准化。这不能算一个合理的梦想,可正如很多人指出的一样,世界从来不为循规蹈矩的人而改变。

总结:扩充程序库比扩充语言要好。我们对待语言扩充要谨慎小心,多方求证;而在对待一个新的程序库——特别是当这个库能够扩展系统程序设计的可移植性的时候——我们则应该勇敢果断,敢于探索。

标准和现实

对C++社区而言,ISO标准委员会是重要的,可很明显,它也仅仅是影响C++程序发展的诸多力量中的一小部分。我在此并未提到其他的语言,可C++的天 性就是能够与大量系统和程序库协同工作。而这一点,影响了我们对语言特性和程序库的看法。更进一步地说,很多语言改进建议和库扩充建议或多或少都是以“向 X语言一样做”这样的要求开始的。我想也许几乎所有语言的较优秀的特性几乎都,在不同时间,曾被建议加入C++中。作为教授,很多标准委员会的成员都对大 量的程序设计语言非常熟悉,而我们的经验又会指引着我们做设计决策。请参考[9],那里阐述了C++和ISO C之间一些特定而微妙的议题。

C++0x的发展必须及时,而且得到编译器产商的认同,新的语言特性实现起来必须比C++98中最难的容易。

作为程序员——而且是一个在多种系统上工作的程序员——我们并不喜欢语言中的方言。可这些方言曾经而且也将继续存在。这些非标准的特性主要来源于与操作系 统、数据库、中间件的绑定;支持“准标准”是另外的一个原因;而第三个原因就是为了对一小部分人群提供支持,而所提供的特性与C++社区的绝大部分并无关 系。我的建议是:只要可能,请尽量避免使用非标准的特性。当避无可避时——这种情况在很多主要系统中都存在——我建议将那些非标准的用法局部化并通过 ISO C++为其提供一个标准的存取界面。C++对通用抽象机制的改善很大一部分原因就是让包装这类非标准代码更容易一些。作为替代,可以使用编译器提供的 lock-in机制。(The alternative is vendor lock-in)

一个例子:范型编程支持

考察下面的代码:

template class vector {

// ...

void push_back(const T&) { /* _ */ }

// ...

};

vector v;

v.push_back(1.2);

v.push_back(2.3);

v.push_back(3.4);

这 里基本上就是对模板和STL的应用。通过大规模的现实世界的应用和大量的实验,我们可以对那些成功的非常有弹性的语言特性和编程技术进行深入的思考;请参 考STL和Boost。特定地,在效率成为关键问题时,模板已经成为了标准的解决方案。那么,在C++0x中我们如何才能做得更好?这里的“更好”,我的 意思是如何在与C++98相较没有效率损失的情况下,获得更优雅的表现形式;而现有的方式又有何问题?说得更具体一点,上面给出的这个vector的例子 是否能够进一步改善?那些重复使用的push_back()即烦人又丑陋,而没有明确指出元素类型则让出错信息复杂难解,更完美的情况下,我还不想暴露 push_back()的实现。一个更好的版本的代码可以是这样的:

template class vector {

// ...

void push_back(const T&); // just the declaration

// ...

};

vector v;

v.push_back(1.2);

v.push_back(2.3);

v.push_back(3.4);

这 里,使用Value_Type来指定元素类型T是一个“concept”;指明了vector对T所作的所有假设。在此基础上,我们可以校验double 是一个Value_type,我们可以在没有push_back()定义的情况下对v作类型检查。当然,如果我们想要在线化,那我们需要定义 push_back(),可这已经成为实现细节,无需考虑了。Concepts的概念使得我们可以在各转换单元内独立地进行类型检查,而无需引入模板参数 的一整套的类型信息;并且有助于提高使用模板时的编译期效率。

那我们如何才能移除对push_back()的重复调用呢?我们可以允许vector利用一个初始化链作为其参数。这要求提供一个以初始化链做参数的构造函数。例如:

template class vector {

// ...

vector(const T*, const T*); // sequence constructor

// ...

};

vector v = { 1.2, 2.3, 3.4 };

concepts和通用的初始化链的细节问题仍然在紧张的讨论之中。这里的重点不是细节,而是我认为这些通用设施与C++0x实际上休戚相关。

会发生吗?

我认为C++0x将会很清晰地展示出这里给出的“概要原则”。然而,资源的匮乏(时间,人力以及其他)限制了我们的能力,并且很明显,我们将会犯一些错 误。同样的,一些“随机扩充”也会趁机混入其中,在整个语言中形成一些“古怪的孤岛”(就如同C和C++中的枚举一样多!)。尽管如此,对于整个C++用 户群及其更多的将来的用户而言,仍然有理由期待,C++0x将是对现有C++的一个重大的改进。


C++0x展望[语言核心进化

声明:本文大部分内容摘自发表在2005年11月期《程序员》杂志上的<C++0x前瞻>一文。为尊重版权,谢绝转载。


刘未鹏 /文

C++标准委员会案头的C++0x提案[1]的与日俱增,tr1[2]的尘埃落定,tr2[3]的浮出水面,C++0x的脚步声似乎是离我们越来越近了,近到我们似乎能够嗅到她的气息,Bjarne StroustrupCUJ上发表的一篇文章『The Design of C++0x[4]高屋建瓴地指出了C++0x的精神,指出了哪些东西可以进入标准,哪些东西应该慎重考虑,哪些东西是迫切需要的。尽管如此,改革还需一步一步来,C++0x中 最关键的改革,即语言改革,必须慎之又慎,所有待加入语言的特性都必须经过认真的商榷,斟酌其必要性(有无效果等同的替代方案)、跟其他语言特性之间的关 系、对现有代码的影响等问题,如果有必要的话,还须对其进行初步实现、使用,并获取相关的经验,最后才能确定是否加入标准。相比之下,标准库的进化则要开 放得多,artima开发社区上发布了一个C++0x Wish List[5]C++开发者可以把自己希望的特性添加进去,由C++标准委员会来仲裁取舍,实际上Bjarne Stroustrup希望C++0x的标准库能够成为一个平台(platform),而不仅仅是库。

C++标准委员会的主页上已经有了大量关于语言改革的提案,几乎涵盖了一门现代的general-purpose语言所应该具备的所有特性提案,当然,除了C++已经做得很好的那些方面之外。然而,C++0x不可能把所有这些东西都放到语言当中去,那样只会让这门语言变成一个拼凑起来的庞然大物,并同时更增加语言的复杂性和学习曲线的陡峭程度;另一方面,虽说C++提倡保持语言内核的紧凑高效,尽量使用库的形式来实现一些特性,而不是总诉诸内建的语言特性,然而有些特性倘若没有语言级别的支持是很难达到它应有的效果的。譬如说Move Semantic如果没有语言级别支持,就得费牛劲才能达到还算凑合的效果,而一旦有了语言级别的支持,即只需对标准作一点小小的改动,就立即达到完美的效果,甚至还能完美解决其他一些问题。更进一步,譬如说像reflection这种特性,倘若没有语言标准结合编译器支持,要想单凭一个库来实现几乎是不可能的,即便跌跌撞撞地实现出来了,其易用性也必定相当差。

本文重点考察到目前为止有关C++0x的语言进化的提案当中的比较突出的一些提案,从中我们可以管窥到C++0x语言核心进化的大致方向以及一些既定的东西。

2002年九月,C++标准委员会的语言核心工作组收到一份提案,“A Proposal to Add Move Semantics Support to the C++ Language”,这份提案由Peter DimovDavid Abrahams以及Howard E. Hinnant发起,Peter DimovBoost.Bind等库的作者,David AbrahamsBoost库的发起者、C++标准委员会的成员、Boost.MPL等库的作者。Howard E. Hinnant也既是Boost库里面的若干库的参与者又是C++标准委员会的成员,这份提案针对C++中长久以来被称为是效率的最大敌人——临时对象——提出了详尽的应对措施。其核心理念在于:之所以临时对象成为C++效率的最大敌人,是因为创建一个具有non-trivial构 造函数的临时对象需要耗去一定的时钟周期和系统资源,而这些动作的全部意义只是创建了一个转瞬即逝的对象,当被(深)拷贝到某个具名对象之后,这个临时对 象就会被析构掉,因而它所占有的资源和分配它们所消耗的时钟周期的全部意义就仅仅在于在栈上临时保持某些东西的一份副本而已。然而实际上,一个有趣的观察 认识到,既然临时对象是没有名字的,那么除了从它进行拷贝的那个对象之外,再没有其他地方能够用到它,这就是说,在进行拷贝的时候,我们可以将临时对象所 占有的资源过来,而不用自己去重分配一份,反正被的那个临时对象立即会被析构掉,因此这个的动作是安全的。这种资源的能力在另一类场合下也是有用的,即你知道某个具名对象在这次被拷贝之后就立即被丢弃了,举个例子,当std::vector重新分配它的内部缓冲区时,它会新分配一块row memory,然后将老内存当中的那些对象一一拷贝到新内存当中去,实际上这种拷贝可能是相当低效的,假设vector中存放的是std::string,后者自己会维护一块内部字符缓冲区,这样一来每拷贝一个string就会进行一次分配,而旧元素中的那些现成的资源则会在析构时被丢弃掉,非常可惜,为何不直接把原来那些string对象的内部缓冲区过来呢?反正它们也用不着了,换句话说,这种动作不再是拷贝,而是搬移(Move),后者更为形象,甚至更理所当然。这是完全可行的,然而,要想进行合法的“偷窃”,我们就必须能够区分一个对象是否为临时对象,只有临时对象或者主动请求被搬移的对象的资源才可以被“转让”,通常情况下这也就是去判断它是否为右值。然而在目前的C++标准下,我们没有直接的办法来判断某个对象是否为右值,Andreu Alexandrescu曾在C++ Users Journal 2003年二月刊上发表了一篇文章“Move Constructor”[6],引入了一个精巧的框架(mojo)来解决这一问题,并在04年六月刊上又发表了一篇相关文章,将move语义实际引入了vector当中,创造了一个被称为yasli::vector的高效vector类。然而,虽说mojo可以说是从原则上完美解决了这个问题,但由于其实现依赖于一系列的trick,因此使用起来有一定的难度,初学者很难将其运用自如。因此要想真正将这一能力广泛运用到实践当中,仍然需要语言内建的支持,因而该提案引入一个所谓的右值引用,将获知对象左右值性质的能力暴露给程序员,从而完美地解决了这个长久以来的效率问题。

无独有偶,随后不久,Peter Dimov又发表了一项提案“The Forwarding Problem” [7],这项提案实际上触及了泛型编程中的一个相当重要的问题——实参转发:由于负责转发的模板函数通常并不知道实参的真正类型,而它们同时又要保证在转发的过程中不改变实参的左右值属性跟cv-qualifier,这就是要求我们有这样一种表述参数类型的语法形式:如果实参是左值,其类型就会被推导为引用,如果实参是右值,其类型又必须带有“右值”语义(注意,目前的模板参数推导机制已经能够正确推导出实参的cv属性)。实际上,解决方案是对右值引用的模板参数推导机制作一点小小的修改,就完美地解决了这个问题。因而右值属性这一语言特性的加入可以说是一石二鸟。

我们可以看出,上面这两项语言进化其实都是依赖于一个重要的能力,即判断一个对象是左值还是右值的能力,由于一些历史性的原因,当时的语言规则在这个方面只有一条,即不能将non-const引用绑定到右值,而实际上mojo正是利用这个语言的缺口来实现左右值的判断的,除此之外即将加入Boost下一版的Boost.FOREACH库也使用了另一个匪夷所思的技巧来探测对象的左右值。这正表明了,由于语言规则本身并不完备,所以mojo(和Boost.FOREACH)才需要借助于一系列的辅助措施以及艰深晦涩的trick。由此可见,一个完备的语言规则系统对于语言本身的表达能力仍是相当重要的。C++语言进化的隐含原则之一就是让语言对初学者变得更易于学习,如今的C++语言中trick太多,其中很多都是利用的一些非常细微的语言特性,所以语言进化的目标之一就是给这些trick以更优雅直观的替代方案。

C++泛型编程的目标除了泛化复用代码之外,还有非常重要的一点就是效率,后者从本质上来说就是将决策提前到编译期。而要将决策提前到编译期,就意味着要让开发者尽可能地获取编译期所能知道的信息,换句话说,C++应该从语言特性的层面上提供给开发者用于获取各种有用的编译期信息的能力。右值引用就是一个绝佳的例子,原先C++中只有左值引用,导致没法判断一个对象是否为右值,至多能够判断出一个对象是“右值或const对象”(借助于“右值无法绑定到non-const引用”这一语言规则),进而导致一些重大的优化无法进行。因而如果罗列一下至今的C++0x语言核心提案中的“十大热门”的话,右值引用肯定位居三甲。

GPC++的灵魂之一,虽说GP近几年一直处于不断的发展和成熟当中,同时也不乏一些激动人心的新技术的出现,甚至有了相当完善的元编程库Boost.MPL[8],但C++语言对GP本身的支持仍然还有诸多不尽人意之处,这些不尽人意的方面有的属于枝节问题,譬如说template aliases[9]extended friend declarations[10]、模板函数的偏序规则的一些不完善之处[11]等等,这些在C++0x里面肯定会得到采纳或者精化。而另外一些则是触及到了GP的根基的问题,譬如concept[12]auto&decltype[13]variadic templates[14]这些特性。

C++ GP其实就是在强类型系统的坚实根基上架空起一层形式上(近乎)无类型的系统,这一层系统是由模板相关的语言特性来支持的,在这个系统当中,你通常看到的是TUtypename,而不是intdoublelong,所有对象的类型都处于一种存在但不被关心的状态,直到模板被实例化,类型被落实,这时强类型系统才会运作起来,进行最后的把关——强类型检查,某种程度上,C++ GP有点像戴着枷锁的舞蹈,强类型系统就好比一把枷锁,而其上的GP设计空间则进行着近乎无类型的舞蹈。这里的问题在于,在目前的C++标准下,模板的绝大多数类型检查都是在其实例化的时候被进行的,那些能够在编译器阅读模板定义时就得到类型检查的部分只是一些non-dependent names[C++98:14.6.3],这在一个模板定义当中通常只占极少一部分。然而,如果编译器在看到模板定义的时候就能够对它进行全面的类型检查的话,就能够有力地提高GP中的早期纠错。STL中引入了concept的概念,并通过一些trick实现了concept的统一表示,然而其作用说到底也只能是在模板实例化时对模板参数进行检查而已(从而提供更为友好的编译错误信息),最关键的是,即便某个函数模板的类型参数应当符合某个concept,编译器也无法根据这个concept来对该函数模板的定义体进行早期的强类型纠错,这是因为concept在当前的语言当中并非一类公民,我们无法在语言层面直接表示一个concept,换句话说编译器并不知道concept的存在。从另一个方面来说,concept其实就是契约式设计(DbC)的编译期版本,我们通常看到的DbC是针对运行期值的,而concept则针对类型,一个concept其实就是在模板定义跟实例化该模板的模板实参之间架上了一层契约(contract),双方都应当遵从或假设这些模板参数是符合特定的concept的,对于使用方来说,其提供的模板实参自然会在模板实例化时得到检验,而对于编译器来说,则可以根据对模板参数的先期假定(concept)来对模板定义体进行早期的类型纠错,同时由于编译器能够知道并理解concept,因而如果出现类型错误的话必然能够生成友好得多的错误信息,从而解决C++98模板的一个令人头疼的大难题。此外,一个first classconcept语言特性还能够极大程度上削减模板的文档工作,因为concept自身就是最好最精确的文档。不过,C++98并未提供这种能力,我们无法在模板定义的时刻告诉编译器哪些模板参数被假定为符合哪些concept。鉴于此,Bjarne Stroustrup一直致力于为C++0x提供一个first class的语言特性,用于表达concept[15]这一概念。目前这个语言特性正在被不断精化,concept的两种可能的表达方式也正在被权衡着利弊,虽然一切尚处于动荡之中,然而可以肯定的是,它肯定会被加入到C++0x当中去,从而为GP整体的易用性带来极大的改善。

另外,一个已经获得C++委员会投票通过的语言特性是autoauto提供一种基于初始化表达式的类型来推导被初始化变量类型的能力。这个特性相当简单直观,然而很重要,因为它进一步从形式上将C++GP往无类型的境界推进了一大步:C++模板编程当中经常会出现一些比较复杂的类型(尤其是当涉及到Boost.Lambda这类表达式模板库的时候),即便在模板定义中也常常如此,但大多数时候我们并不关心某个变量的类型是什么,只是关心它有哪些操作或语义,而C++的强类型系统却迫使我们在声明一个变量的时候给出其类型,这就给代码中塞入了不优雅的成分,为了解决这个问题,提案中引入了一个新的关键字:auto,其作用可以说成是一个占位符——既然我们不关心变量的类型而又必须以某种形式来声明其类型,那么就用一个“auto”来占据那个类型的位置吧,这个特性在丝毫不影响强类型系统的同时极大地简化和优雅了模板代码,因而以压倒性的优势获得投票通过。

另一个能够极大地提升C++模板机制的泛化表达能力的特性是variadic templates。当前的C++标准并不能表达“可变数目的模板参数列表”这一概念,因而像tr1::tupletr1::bind乃至tr1::function(这三个原先是boost中的设施目前已经进入tr1)这些模板就必须借助于一些非常繁琐的技巧来实现可变参数列表的能力,而且这种模拟的可变参数列表也只能算是个半成品,譬如tr1::tuple实际上接受的模板参数个数是有限的,而tr1::bind也最多也只能绑定有限多个参数,Boost.Bind里面目前实现到了绑定九个参数就已经引入了大量的重载,不但增加了重复代码量,而且很大程度上增加了编译时间。而Boost.Function为 了支持不同数目参数的函数,则是通过一些非常晦涩复杂的宏技巧来实现的,非但降低了代码的可读性,同时也增加了代码量和编译时间。因而一个语言层面支持的 可变模板参数列表似乎是必然的要求,有了这一特性,这三个类模板的实现以及其他涉及可变模板参数列表的模板代码都可以得到极大的简化,我们可以从提案当中 看到这一点。目前variadic template这一提案仍然处在不断精化当中,但我们有理由相信它一定会出现在C++0x当中。

显而易见,有关语言核心进化的绝大部分重大提案都是与模板相关的,因为模板是C++区别于其它语言的重要标志之一,也是在C++当中获得重大成功的特性,其带来的影响可以说是革命性的,我们有理由相信C++0x中的模板会带来再一次的革命。

此外,有望进入C++0x的语言核心进化提案还有诸如”nullptr””delegate constructor””strong typed enum””opaque typedef””explicit conversion operator”等,这些提案的内容顾名思义。与上面提到的提案相比,这些提案只能算是对当前语言核心中的一些小修补或精化,改善语言的表达力,使C++0x的编码变得更为优雅。除此之外我们还可以在C++标准委员会的网站上看到一份”C++ Standard Core Language Issue List”,里面是众多关于当前的语言核心的一些缺陷或模糊之处以及解决方案,这些都会成为C++0x对语言核心进行精化的依据。从98年第一代标准颁布至今,C++标准委员会获得了许多宝贵的经验,C++0x这个第二代标准必然会比C++98成熟得多。

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics