2.1 第一个十年
后来成为了 C++ 的东西始于 1979 年 4 月,名为带类的 C(C with Classes)。我的目标是设计一个工具,它既拥有直接而高效的处理硬件的能力(例如编写内存管理器、进程调度器和设备驱动程序),又同时可以有类似 Simula 的功能来组织代码(例如强
静态可扩展类型检查、类、多级类和协程)。我想用这个工具编写一版 Unix 内核,可以在通过局域网或共享内存互联的多个处理器上运行。
我选择 C 作为我工作的基础,因为它足够好,并且在办公室里就能得到很好的支持:我的办公室就在 Dennis Ritchie 和 Brian Kernighan 走廊对面。然而,C 语言并不是我考虑的唯一语言。Algol68 当时深深吸引了我,我还是 BCPL 和其他一些机器层面的语言的专家。C 后来的巨大成功在当时还完全不确定,但是 Brian Kernighan 和 Dennis Ritchie 杰出的介绍和手册 [Kernighan and Ritchie1978] 已经出现,Unix 也正开始它的胜利路程。
最初我实现的是一个预处理器,它将带类的 C
差不多逐行翻译成 C。1982 年,在带类的 C
的用户数量增长到了几十人的时候,这种方法已经显得无法把控了。所以我写了一个传统的编译器,叫作 Cfront, 1983 年 10 月第一次给别人使用。Cfront 是一个传统的编译器,它有一个词法分析器、一个构建抽象语法树的语法分析器、一个用类型装饰语法树的类型检查器,以及一个重新排列 AST 以提高生成代码的运行期效率的高层次优化器。关于 Cfront 的本质有很多困惑,因为当时它最终输出的是 C(优化的,不是特别可读的 C)。我生成了 C,这样我就不必直接处理当年正在使用的众多的(非标准化)链接器和优化器。不过,Cfront 一点也不像传统的预处理器。你可以在计算机历史博物馆的源代码收藏 [McJones 2007–2020] 中找到一份带有文档的 Cfront 源代码。Cfront 从带类的 C
自举为 C++,所以第一个 C++ 编译器是用(简单的)C++ 写的,适合非常小的计算机(内存小于 1MB,处理器速度小于 1MHz)。
带类的 C
添加到 C 上的第一个特性是类。我从早期在 Simula 中的使用中了解到它们的力量,在 Simula 中,类是严格静态、但又可扩展的类型系统的关键。我立即添加了构造函数和析构函数。它们当时非常新颖,但从我的计算机架构和操作系统背景来看,我认为它们 也不算很新奇,因为我需要一个机制来建立一个工作环境(构造函数)和一个逆操作来释放运行期获得的资源(析构函数)。以下摘自我 1979 年的实验记录本:
new 函数
为成员函数创建运行的环境delete 函数
则执行相反的操作
new 函数
和delete 函数
这两个术语是构造函数
和析构函数
的原始术语。直到今天,我仍然认为构造函数和析构函数是 C++ 的真正核心。另见(§2.2.1)和(§10.6)。
当时,除了 C 语言,基本上所有语言都有适当的函数参数类型检查。我认为没有它我无法完成任何重要的事情。因此,在我的部门主管 Alexander Fraser 的鼓励下,我立即添加了(可选的)函数参数声明和参数检查。这就是 C 语言中现在所说的函数原型。1982 年,在看到让函数参数检查保持可选的效果后,我将其设为强制的。这导致了十几二十年里关于与 C 不兼容的大声抱怨。人们想要保留他们的类型错误,或者至少许多人大声说他们不想检查,并以此作为不使用 C++ 的借口。这个小事实也许能让人们认识到演化一门被大量使用的语言会涉及到的各种问题。
鉴于过于狭隘的 C 和 C++ 爱好者之间偶尔会恶语相向,或许值得指出,我一直是 Dennis Ritchie 和 Brian Kernighan 的朋友,在 16 年里几乎天天同他们一起吃午饭。我从他们那里学到了很多,现在还经常同 Brian 见面。我将一些对 C++ 语言的贡献 [Stroustrup 1993] 归功于他们两位,而我自己也是 C 的主要贡献者(例如函数定义语法、函数原型、const
和 //
注释)。
为了能够理性思考 C++ 的成长,我想出了一套设计规则。这些在 [Stroustrup 1993, 1994] 中有介绍,所以这里我只提一小部分:
- 不要陷入对完美的徒劳追求。
- 始终提供过渡路径。
- 说出你的意图(即,能够直接表达高层次的思路)。
- 不要隐式地在静态类型系统方面违规。
- 为用户定义类型提供和内置类型同样好的支持。
- 应取消预处理器的使用。
- 不要给 C++ 以下的低级语言留有余地(汇编语言除外)。
这些目标的野心并不小。其中某些目标,现在 2020 年了我依然在为之努力工作。在 1980 年代早期到中期,我给 C++ 添加了更多的语言功能:
- 1981 年:
const
——支持接口和符号常量的不变性。 - 1982 年:虚函数——提供运行期多态。
- 1984 年:引用——支持运算符重载和简化参数传递。
- 1984 年:运算符和函数重载——除了算术和逻辑运算符外,还包括:允许用户定义
=
(赋值)、()
(调用;支持函数对象(§4.3.1))、[]
(下标访问)和->
(智能指针)。 - 1987 年:类型安全链接——消除许多来自不同翻译单元中不一致声明的错误。
- 1987 年:抽象类——提供纯接口。
在 1980 年代后期,随着计算机能力的急剧增强,我对大型软件更感兴趣,并做了如下补充:
- 模板——在经历了多年使用宏进行泛型编程的痛苦之后,更好地支持泛型编程。
- 异常——试图给混乱的错误处理带来某种秩序;RAII(§2.2.1)便是为此目标而设计的。
后面这些功能并没有受到普遍欢迎(例如,见(§7))。 部分原因是社区已经变得庞大和难以管理。ANSI 标准化已经开始,所以我不再能够私下实现和实验。人们坚持大规模的精心设计,坚持在认真实施之前进行广泛的辩论。我不能再在明知道不可能让每个人都满意的 情况下,从一个最小的提议开始,把它发展成一个更完整的功能。例如,人们坚持到处使用笨重的带有 template<class T>
前缀的模板语法。
在 1980 年代末,面向对象
的宣传变得震耳欲聋,淹没了我对 C++ 传达的讯息。我对 C++ 是什么和应当成为什么的看法被广泛忽视了——很多人甚至从未听说过。对于面向对象
的某些定义来说,所有新语言都应是纯面向对象的
。不真正面向对象
被视为是糟糕的,不容争辩。
我从未使用过C++ 是一种面向对象的编程语言
这种说法,这件事很多人并不知道,或者因为感到有些尴尬而有意忽略了。那时候,我的标准描述是
C++ 是一门偏向系统编程的通用编程语言,它是
- 更好的 C
- 支持数据抽象
- 支持面向对象编程
- 支持泛型编程
这个说法过去和现在都是准确的,但不如万物皆对象!
这样的口号那么令人兴奋。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论