C++语言的设计与演化
概述
《The Design and Evolution of C++》 是 C++ 之父 Bjarne Stroustrup 为描述 C++ 语言的发展历史、设计理念和技术细节的著作。本篇文章对该书做一些精要部分的摘抄,并记录一些自己的心得。
历史
(在作者看来,语言的设计要考虑到许多实践性的和社会性的因素,初始设计往往是满足当时的特定问题,后来成才去满足新的需求,反映对问题和对解决它们的工具和技术的新理解。)
我始终不渝的信念是,所有成功的语言都是逐渐成长起来的,而不是仅根据某个第一原则设计出来的。
(很多语言,例如 Lisp,在设计时就指定了很多原则,如函数的第一性原则、函数式、基于序对的表结构是所有数据结构的基础等等,Lisp 很难说可以去尽可能满足现实中的需求。)
C++ 之前的历史
Simula 语言和分布式系统
(Simula 语言是被认为是第一个 OOP 语言,它引入了 OOP 的基本概念:对象、类和继承。)
(在作者看来,在没有合适工具的情况下绝不去冲击一个问题,作者总结了一个“合适的工具”应该是什么样的。)
- 对程序组织的支持(类)、对并发的某种形式的支持,以及对基于类的类型系统的强检查。
- 构建、运行都要快(至少能够运行得像 BCPL 一样快)。
- 允许高度可移植。
(在作者看来,这些准则和语言的任何特定特征没有任何关系。相反,它们只是提出了对解决方案的一些约束条件。)
我在操作系统领域的知识基础,以及对于模块化和通信的兴趣对于 C++ 有持久的影响。例如 C++ 的保护模型就来自于访问权限许可和转让的概念;初始化和赋值
的区分来自于对转让能力的思考;C++ 的 const 概念是从读写保护机制中演化出来的;而 C++ 异常处理机制的设计则受到 Brian Randell 小组在容错系统工作的影响。
C语言
(作者分析了自己的实用主义和经验主义哲学倾向,倾心齐克果的存在主义哲学,认为其对个人的几乎狂热关心以及敏锐的心理洞察力比黑格尔及马克思更具有感染力。)
(这影响了 C++ 被有意地设计成能够支持各种各样的风格,而不是强调“一条真理之路”。)
带类的 C 语言
(语言要保持通用型,而不是面向特定的应用领域,当需要支持时,就应该通过库或者特定的扩充来做这件事。)
来类的 C 应该能够用到 C 可以使用的一切地方。这还意味着需要取得与 C 相当的执行效率。
(早期带类的 C 是通过预处理程序实现的,C 的全部能力都是可以使用的 )
类
(类 是最重要和核心的概念。但不要把它看得过于神秘,我们可以说类是一种抽象机制,在数据和数据操作方面,又是一种封装机制。)
对象是类的实例化,或者说前者由后者创建,这和基本数据类型(如 int、float等)的变量的创建是一致的。
函数定义通常写在“其他地方”,以使得类的声明看起来就像是一个接口描述,而不像是为了组织代码而提供的一种语法结构,这也意味着可以分别编译。
new() 是构造函数,后来演化为 C++ 里面的构造函数,不过函数名使用类名,而 new 则作为保留关键字用于动态分配对象的内存空间。
效率
用户定义的类型和内部类型在语言法则上要保持一致性,即都可以定义局部或者全局变量(Simula没有做到这一点)。
内联
引入内联的一般性理由与越过保护屏障的代价有关。…… 在带类的 C 中只有成员函数能做成 inline 的,做法只有一种,就是把它的函数体放进类的声明中。
后来 C++ 引入了 inline 关键字并允许内联非成员函数。
inline 关键字只是一种让编译器尽可能去内联的提示,编译器可以忽略它。
link
按名字等价是 C++ 类型系统的基石,而内存布局相容性则保证了可以使用显式类型转换,以便能够提供低级的转换服务。
(相反,在 python 语言中可以按照结构等价的,在底层内存数据层面,相同名字的变量引用的是同一块只读存储区。)
惟一定义原则:在 C++ 中,每个函数、变量、类型、变量、常量等都应该恰好有一个定义。
(在作者看来,带类的 C 要考虑一种纯朴的实现,不使用复杂的、花哨的和不实用的算法或机制。这种简单性和实用性可以快速培养用户的满足感与成就感。)
(在作者看来,C++ 是一个系统中的一个语言,而不是系统本身,不像 Smalltalk 或者 Lisp 那样被认为是完整的系统或者环境。)
能够与其他语言和系统共生的问题至今还明显不是大多数理论家、所谓的完美主义者、学术界用户们的关注点。但我相信这是 C++ 成功的一个主要原因。
和 C 的连接兼容性要比代码的兼容性重要得多
对象的内存布局模型
class 的布局模型和 struct 完全一样。this 指针的使用。
静态类型检查
对调用不加声明函数是否需要检查以及如何检查的讨论。带类的 C 引入了 void 关键字。
是否支持隐式窄转换(如 int -> char)的讨论。(不阻止,但会报告警告信息,但多数程序员往往忽略编译器的警告信息)
语言设计并不是从某个第一原理出发的设计,而是一种需要经验,试验和有效工程折衷的艺术。
语法
C++ 引入的很多对 C 的语法的改进最后也被 ASNI C 标准所接受,但作者的原则是一贯的:对 C 用户的兼容是第一位的。不会做激进地改进,“什么都不做就什么都做了”。
派生类与虚函数
没有虚函数时的多态性:用户可以使用派生类的对象而把基类当作实现细节。
(显式控制机制越多,越精细,系统就越复杂!)
保护模型
- 保护是通过编译时机制提供的,目标是防止意外事件,而不是防止欺骗或者有意的侵犯。
- 访问权由类本身授予,而不是单方面的取用。
- 访问权控制是通过名字实行的。
- 保护的单位是
类,而不是个别的对象。 - 受控制的是访问权,而不是可见性。
“去做一些有趣的事情。”
工作环境
像所有语言一样,C++ 也需要为了生存而在其儿童时代就开始工作、它在成熟时带着一条实践和现实主义的绶带,也带着累累伤痕。基于作业库的对于网络、电路板布局、芯片、网络程控等等的模拟,这些都是在早期年代里我的面包和黄油。
C++ 的诞生
虚函数
我认为,让 struct 与 class 始终作为同一个概念,实际上也将我们拯救了出来,没有去把类实现为一种支持高代价、多样化、具有和目前很不相同的特征的东西。
语言设计的工具
我的牢固信念是,语言设计并不是纯粹的思维训练,而是一种在需要、想法、技术和约束条件之间取得平衡的非常实际的修炼。一个好的语言不是设计出来的,而是成长起来的。这种修炼与工程、社会学、和哲学的关系比数学的关系更密切一些。
正交性应该是第二位的原则—— 放在对于有用性和效率的最基本效率之后。
良好设计的关键是对问题的深入认识,而不是提供了多少最高级的特征。
C++ 语言的设计规则
一般性规则:
- C++ 的发展必须由实际问题推动
- 不能牵涉到无益的对完美的追求之中
- C++ 必须现在就是有用的
- 每个特征必须存在一种合理的明显实现方式
- 总提供一条转变的通道
- C++ 是一种语言,而不是一个完整的系统
- 为每种应该支持的风格提供全面的支持
- 不试图强迫人做什么
设计支持规则:
- 支持一致的设计概念
- 为程序的组织提供各种机制
- 直接说出你的意思(语言能够提供更强的表达能力)
- 所有特征都必须是能够负担的:
- 允许一个有用的特征比防止各种错误使用更重要。
- 支持从分别开发的部分出发进行软件的组合。