立足现实 与时俱进:C++ 1991-2006
摘要
本文概述C++程序语言的历史,从早期的ISO标准化(1991)到1998年成为ISO标准,到稍
后的C++0x标准修正版本(2006)。重点在于阐述理想、约束、编程技术和那些塑造语言的人
们,而不是语言特征的细节方面。其中主要的主题有泛型编程和STL(C++标准库的算法和容器)。
特定的主题包括模板分离编译、异常处理和对嵌入式系统编程的支持。在大部分时期内,C++
是一个有着百万级用户的成熟的语言。因此,本文讨论各种C++的使用、技术以及商业的压力,
这些都是C++持续演化的背景。
分类和主题描述 K.2[计算历史]:系统
普通术语 设计,编程语言,历史
关键字 C++,语言使用,演化,库,标准化,ISO,STL,多范例编程
1、 导言
在1991年10月,估计的C++用户的数字是400,000[121];而到了2004年10月,相应的数字
是3,270,000[61]。在90年代初C++用户不再呈指数增长但在以后的10年中,C++用户稳定增长。
那段时间的工作主要有以下几个方面:
1、使用语言(显然);
2、提供更好的编译器、工具和库;
3、避免语言分裂成各种方言;
4、避免语言和它的社团停滞不前;
显然,C++社团在第一项工作上花费了大量的时间、金钱;ISO C++委员会则倾向于把精力放
在第二和第三项工作上;我的主要工作是放在第三和第四项。ISO标准委员会是那些想要改进
C++语言和标准库的人们的中心。通过这样的改变,他们(我们)希望能够改进在现实世界中
C++编程的艺术地位。C++标准委员会的工作主要放在C++的演化上。
认为C++是用于应用程序开发的平台,许多人想知道为什么—在C++成功之初—C++为什
么没有抛弃它从C继承到的东西,走向“食物链的顶层”,成为一个“真正的面向对象”的带“完
整”标准库的应用程序编程语言。任何向那个方向转变的趋势都被系统编程、与C的兼容性、同
标准委员会有关联的许多社团中使用的C++早期版本兼容所压制。同时,在社团中永远不会有
充足的资源用于大工程。另一个重要因素就是由主要软件、硬件提供商带来的朝气蓬勃的商业投
机主义,他们看到了C++创建的应用程序是用来区别出他们和他们的竞争对手并且锁定他们用
户的机会。其它玩家也看到了这一点,但他们的目标是成长和繁荣。不管怎么样,在委员会所选
择的活动领域内并不缺乏支持,提供商的系统、高要求的应用程序依赖于C++。因此,他们提
供稳定和最有价值的支持用于标准化工作,通过主持会议的形式,并且—更重要的是—关键的技
术人员的参与。
不管好坏,ISO C++[66]仍是一个通用的编程语言并且偏向系统编程,有以下特点:
1、更好的C;
2、支持数据抽象;
3、支持面向对象编程;
4、支持泛型编程;
从历史的观点对前三项的解释可以[120,120]中找到;对支持泛型编程的解释是本文的重点主
题。把泛型编程引进主流很可能是C++在这个时期对软件开发社区的最大贡献。
本文的以散乱的年表为次序进行组织:
§1 导言
§2 背景:1979 -1991的C++—早期的历史,设计标准,语言特征
§3 1991年的C++世界— C++标准化进程,年表
§4 标准库设施 1991-1998 — C++标准库,重点强调它的最重要和最具创新的组件:STL(容
器、算法和迭代器)
§5语言特征 1991-1998 —集中在分离模板编译、异常安全、运行时类型信息和名字空间
§6 标准维护 1997-2003 —为了稳定性,C++标准没有增加新的内容;然而,委员会并没有因
此而闲着
§7 C++在真实世界中的使用—应用程序领域;应用编程vs系统编程;编程风格;库、应用程序
二进制接口(ABI)和环境;工具和研究;Java、C#和C;方言
§8 C++0x—目标、约束、语言特征及库设施
§9 回顾—影响和冲击;展望C++
重点在于早期和后期:早期塑造了现在的C++(C++98);后期则反映了对C++98的经历的反
应也适用于C++0x。离开代码讨论怎么在代码中表达想法不可能的,因此,代码的例子用于说
明关键的想法、技术和语言设施。这些例子的解释也能被那些不是C++程序员的人们所理解。
然而,介绍的焦点是塑造C++的人们、想法、理想、技术和约束,而不是语言-技术细节。对于
描述今天的ISO C++是什么样的,请参考[66,126]。本文的重点在于回答这些直观的问题:发
生了什么事?什么时候发生的?当时谁在场?他们的理由是什么?哪些已经完成(或未完成)?
C++是一个活的语言。本文的主要目标是描述它的演化。然而,它也是一个强调向后兼容
的语言。在本文中我描述的代码在今天仍能编译和运行。因此,我倾向于使用现在时态去描述它。
考虑到对兼容性的强调,这些代码可能在15年后仍然可以运行。因而,我使用现在时态强调C++
演化的一个重要方面。
2、 背景: C++ 1979-1991
C++的早期历史是从我的HOPL-II论文到1991年,在我的《C++程序设计和演化》中指的前
15年。它讲述在1994年之前的C++前历史。然而,为设置C++下一个年代的场景,这里简单总
结一下C++的早期历史。
C++的设计是为了提供类似于Simula的设施(用于程序组织)和C的高效和灵活性(系统编
程)。当时是希望是在有了这些想法的半年之内就将它实现并交付实际的工程使用,它成功了!
在那时,我意识到谨慎或荒谬都不是目标,目标是适度,卷入革新和不合理,在时间标度和严峻
的效率和灵活性需求。如果早期没有引入适当的革新,那么效率和灵活性必须维护并付出代价;
C++的目标进一步被精炼、详细描述并且更加明确,今天所使用的C++直接反映了它的原来目
标。
从1979年开始,当我还在Bell实验室的计算机科学研究中心时,我首先设计了一种C方言,
称之为“带类的C”。从1979年到1983年,这项工作和与带类的C的经历决定了C++的轮廓。另
一个方面,带类的C是基于我在剑桥大学攻读博士生的学习中(分布式系统)使用BCPL和Simula
的经验。为了攻克系统编程任务,我觉得需要一种工具,它具有如下属性:
·好的工具有Simula用于程序组织的组织—类、某种形式的类层次、某种形式的并发支持,和基
于类的类型系统的编译期检查。我把这看成是对创造编程进程的一种支持,换句话说,是对设计
的支持而不是对编译器的支持。
·好的工具产生的程序跟BCPL一样快并且具有BCPL的将多个分离的已编译单元组合成一个程序
能力。简单的链接规定是必不可少的,用于那些用多种语言写的单元,比如C、Algol68、Fortran、
BCPL、汇编等,组合成一个程序,从而不会遭遇单一语言中的内在限制的阻碍。
·好的工具应该允许高可移植性的编译器。我的经验就是“好”的编译器通常不会出现直到“下
一年”并且是在一种我负担不起的机器上才能使用。这就意味着工具必须有多种编译器来源(没
有专利会是对于使用“不常见”机器的用户和没有钱的研究生的一个足够的响应),并且不需要
移植复杂的运行时支持系统,在工具和宿主操作系统之间只有非常有限的结合。
我在贝尔实验室的早些年间,这些理想变成了一组用于C++设计的“经验法则”。
·一般规则:
·C++的演化必须由真实问题所驱动
·不涉及毫无结果地对完美的追求
·C++在现在就必须是有用的
·每个特征必须有一个合理的明显的实现
·总是提供转变路径
·C++是一门语言,而不是一个完整的系统
·对每一种已被支持的风格提供综合的支持
·不强迫人们使用特定的编程风格
·设计支持的规则:
·支持健全的设计观点
·提供用于程序组织的设施
·用语言直接表达你的意思
·所有的特征是负担得起的
·允许有用的特征比防止每一种误用更重要
·支持将独立开发组件组合成软件
·语言-技术规则:
·没有隐式地违反静态类型系统
·对用户自定义类型提供同内建类型一样好的支持
·局部化
·避免顺序依赖
·如果有疑问,选择更容易教学的特征
·句法问题(通常是以错误的方法)
·消灭预处理器的使用
·底层的编程规则:
·使用传统的(没有提示信息)链接器
·除非有其它理由,否则要与C兼容
·在C++之下没有其它低级语言的使用空间(除了汇编程序)
·你不需要为你不需要的东西付出代价(0开销原则)
·如果有疑问,提供手动控制的方法
在D&E[121]第四章中有对这些标准的更详细的探讨。C++在1989年的release2.0发布时就严格
地满足这些标准;基本的压力来自用于C++的模板设计和异常处理的机制,需要背离这些标准
中的某些方面。我认为这些准则中最重要的属性是它们只与特定的编程语言特征只有松散的联
系,而不是说它们强加约束于处理设计问题的解决方法之上。
在2006年,当我再次回顾这个列表时,我惊讶地发现两个设计规则(理想)没有明确的列
在上面:
·有直接将C++
语言构建到硬件的映射
·标准库是具体的且由C++实现
由于是从C走过来的并且长期工作于C++98标准(§3.1)的制定,这些标准(在那时候和更早
期)看起来是这么的显然以致我通常没有去强调它们。其它语言,如Lisp、Smalltalk、Python、
Ruby、Java和C#,并不享有这些理想。大多数语言提供抽象的机机制的同时还需要提供大多数
有用的数据结构,如strings、list、tree、关联数组、vector、矩阵和set,如同内建设施,依赖于
其它语言用于(如汇编、C和C++)它们的编译器。对于多数的语言,只有C++提供由语言本身
所实现一般、灵活、可扩展和高效的容器。从更广的程度说,这些理想来源于C。C有这两种属
性,但没有定义重要的新类型所需要的抽象机制。
C++的理想—从带类的C开始—就是应该允许最佳地实现任意的数据结构和在它们之上的
操作。这就限制了抽象机制的许多有用的方法[121]的设计,并且导致新的、有趣的编译器实现
技术(例如,§4.1)。反过来,如果没有“直接将C++与硬件映射”这个准则,这个理想将很
难达成。主要的想法(来自C)是操作符直接反映硬件的操作(算术和逻辑),对内存的访问操
作也是由硬件直接提供的(指针和数组)。这两个理想的组合同时使得C++可以有效地用于嵌
入式系统编程[131](§6.1)。
2.1带类C的诞生
最终导致C++诞生的工作开始于我们企图去分析UNIX内核,并设法确定怎样才能把它分布
到由局域网连接起来的一个计算机网络上。这项工作开始于1979年4月,在新泽西州Murray Hill
的贝尔实验室计算机科学研究中心。有两个子问题很快就浮现出来:怎么分析由内核分布造成的
网络流量;怎么模块化内核。这两个问题都要求提供一种描述方式,以便描述复杂系统的模块结
构和模块间的通信模式。这正好就是我曾经决定的如果没有合适的工具就绝不去冲击的那一类问
题。因此我决定根据自己在剑桥形成的一套标准去开发一种合适的工具。
在1979年10月,我已经完成了预处理器的雏形版本,叫Cpre,它添加了类似于Simula的类
到C中。到了1980年3月,这个预处理器被改进,并且可以支持一个真实的工程和几个实验。我
的记录显示到那时止,预处理器在16个系统中得到使用。第一个关键的C++库,叫“任务系统”,
支持协程风格的编程[108,115]。它对于带类C的有效性是至关重要的,由于这个预处理器而被
接受的语言被称之为“带类的C”。
在4月到10月这段时期内,从思考一个工具到思考一门语言的转变出现了,但带类的C仍被
认为是对C的一种扩展,用以表达模块化和并发。尽管对并发的支持和模仿Simula风格是带类C
的主要目标,语言本身并不包含表达并发的原语;然而,继承(类继承)和定义能被预处理器识
别的具有特殊意义的类成员函数的组合,被用以编写支持期望的并发风格的库。请注意这里的风
格是复数的。我认为这是至关重要的—我现在仍持此观点—这个语言应该能够表达多种并发的概
念。
因此,语言提供了一般的机制用于组织程序而不是仅支持特定的应用领域。这使得带类的C
和后来的C++成为一门通用语言而不是成为带支持特定应用的扩展的C的变种。后来,提供对特
定应用的支持还是对一般抽象机制的支持的选择复杂出现,每一次,答案总是改进抽象机制。
2.2 特征概览
早期的特征包括
·类
·派生类
·构造函数和析构函数
·公有/私有访问权控制
·类型检查和隐式函数参数转换
在1981年,基于可预知的需求,又添加了几个特征:
·内联函数
·默认参数
·赋值操作符重载
由于带类的C是用预处理器实现的,只有少数的C中没有的特征需要描述,C的全部能力都是
用户所可以使用的。这两个方面在当时都受到称赞。特别地,让C成为其子集显著地减小了所需
的支持和文档的重写工作。带类的C仍被看作是C的一种方言。而且,类被称为是“用于C语言的