最初,我设计 C++ 是为了回答这样的一个问题:如何直接操作硬件,同时又支持高效、高级的抽象?C++ 在 1980 年代仅仅是一个基于 C 和 Simula 语言功能的组合,在当时的计算机上作为系统编程的相对简单的解决方案,经过多年的发展,已经成长为一个远比当年更复杂和有效的工具,应用极其广泛。它保持了如下两方面的关注:
- 语言构件到硬件功能的直接映射
- 零开销抽象
这种组合是 C++ 区别于大多数语言的决定性特征。“零开销”是这样解释的 [Stroustrup 1994]:
- 你不用的东西,你就不需要付出代价(“没有分散开销”)。
- 你使用的东西,你手工写代码也不会更好。
抽象在代码中体现为函数、类、模板、概念和别名。
C++ 是一种活的语言,因此它会不断变化以应对新出现的挑战和演变中的使用风格。2006 年至 2020 年期间的这些挑战和变化是本文的重点。当然,一门语言本身不会改变;是人们改变了它。所以这也是参与 C++ 演化的人们的故事,他们识别出面临的挑战,诠释解决方案的局限,组织他们的工作成果,并解决他们之间必然出现的分歧。当我呈现一种语言或标准库特性时,其背景是 C++ 的一般发展和当时参与者的关切。对于在早期被接受的许多特性,我们现在从大量的工业使用中获得了后见之明。
C++ 主要是一种工业语言,一种构建系统的工具。对于用户来说,C++ 不仅仅是一种由规范定义的语言;它是由许多部分组成的工具集的一部分:
- 语言
- 标准库
- 许多的其他库
- 庞大的——常常是旧的——代码库
- 工具(包括其他语言)
- 教学和培训
- 社区支持
只要有可能,只要合适,我就会考虑这些组成部分之间的相互作用。
有一种流传广泛的谬见,就是程序员希望他们的语言是简单的。当你不得不学习一门新的语言、不得不设计一门编程课程、或是在学术论文中描述一门语言时,追求简单显然是实情。对于这样的用途,让语言干净地体现一些明确的原则是一个明显的优势,也是理想情况。当开发人员的焦点从学习转移到交付和维护重要的应用程序时,他们的需求从简单转移到全面的支持、稳定性(兼容性)和熟悉度。人们总是混淆熟悉度和简单,如果可以选择的话,他们更倾向于熟悉度而不是简单。
看待 C++ 的一种方式是,把它看成几十年来三种相互矛盾的要求的结果:
- 让语言更简单!
- 立即添加这两个必要特性!!
- 不要搞砸我的(任何)代码!!!
我添加了感叹号,因为这些观点的表达常常带着不小的情绪。
我想让简单的事情简单做,并确保复杂的事情并非不可能,也不会没有必要地难。前者对于不是语言律师的开发者来说是必不可少的;后者对于基础性代码的实现者是必要的。稳定是所有意图持续运行几十年的系统的基本属性,然而一种活的语言必须适应不断变化的世界。
C++ 有一些总体构想。我阐述了一些(如《C++ 语言的设计和演化》(The Design and Evolution of C++)[Stroustrup 1994](§2)、设计原则(§9.1),以及 C++ 模型(§11.1))并试图让语言在演化时遵循它们。然而,C++ 的开发由 ISO 标准委员会控制,它主要关注的是长长的新特性列表,以及对实际细节的关心。这是社区里最能表达和最有影响力的人所坚持的东西,仅仅基于哲学或理论观点就否认他们的关切和意见的话,恐怕就失之鲁莽了。
为了给出一个快速的概述,这里有一个粗略的年表。如果你不熟悉 C++,很多术语、构件、库都会晦涩难懂;大多数在以前的 HOPL 论文 [Stroustrup 1993, 2007] 或本文中有详细解释。
- 1979 年:工作始于“带类的 C”,它变成了 C++;拥有了第一个非研究性的用户;
- 语言:
class
、构造函数/析构函数、public
/private
、简单继承、函数参数类型检查 - 库:
task
(协程和仿真支持)、用宏参数化的vector
- 语言:
- 1985 年:C++ 的首次商业发行;TC++PL1 [Stroustrup 1985b]
- 语言:
virtual
函数、运算符重载、引用、常量 - 库:
complex
算法,流输入输出
- 语言:
- 1989–91 年:ANSI 和 ISO 标准化开始;TC++PL2 [Stroustrup 1991]
- 语言:抽象类、多重继承、异常、模板
- 库:输入输出流(但没有
task
)
- 1998 年:C++98、第一个 ISO C++ 标准 [Koenig1998]、TC++PL3 [Stroustrup 1997]
- 语言:
namespace
、具名类型转换1、bool
、dynamic_cast
- 库:STL(容器和算法)、
string
、bitset
- 语言:
- 2011 年:C++11 [Becker 2011],TC++PL4 [Stroustrup 2013]
- 语言:内存模型、
auto
、范围for
、constexpr
、lambda 表达式、用户定义字面量…… - 库:
thread
和锁、future
、unique_ptr
、shared_ptr
、array
、时间和时钟、随机数、无序容器(哈希表)……
- 语言:内存模型、
- 2014 年:C++14 [du Toit 2014]
- 语言:泛型 lambda 表达式、
constexpr
函数中的局部变量、数字分隔符…… - 库:用户定义字面量……
- 语言:泛型 lambda 表达式、
- 2017 年:C++17 [Smith 2017]
- 语言:结构化绑定、变量模板、模板参数的构造函数推导……
- 库:文件系统、
scoped_lock
、shared_mutex
(读写锁)、any
、variant
、optional
、string_view
、并行算法……
- 2020 年:C++20 [Smith 2020]
- 语言:
concept
、module
、协程、三路比较、改进对编译期计算的支持…… - 库:概念、范围、日期和时区、
span
、格式、改进的并发和并行支持……
- 语言:
请注意,早年 C++ 的库是很匮乏的。事实上,当时还是存在大量各种各样的库(包括图形用户界面库),但很少被广泛使用,并且很多库是专有软件。这是在开源开发普及之前的事。这造成了 C++ 社区没有一个重要的共享基础库。在我的 HOPL2 论文 [Stroustrup 1993] 的回顾中,我认为那是早期 C++ 最糟糕的错误。
任务库 [Stroustrup 1985a,c] 是一个基于协程的库,支持事件驱动的仿真(例如随机数生成),与替代方案相比是非常高效的,甚至可以运行在很小的计算机上。例如,我在 256KB 的内存中运行了 700 个任务的仿真。任务库在 C++ 早期非常重要,是贝尔实验室和其他地方许多重要应用的基础。然而,它有点丑陋,并且不容易移植到 Sun 的 SPARC 体系结构,因此大多数 1989 年以后的实现都不支持它。2020 年,协程才刚刚回归(§9.3.2)。
总的来说,C++ 的特性不断增多。ISO 委员会也废除了一些特性,对语言进行了稍许清理,但是考虑到 C++ 的大量使用(数十亿行代码),重要的特性是永远不会被移除的。稳定性也是 C++ 的关键特性。要解决跟语言不断增长的规模和复杂性相关的问题,办法之一是通过编码指南(§10.6)。
这篇论文是按照 ISO 标准发布的大致时间顺序组织的。
- §1:前言
- §2:背景:C++ 的 1979–2006
- §3:C++ 标准委员会
- §4:C++11:感觉像是门新语言
- §5:C++14:完成 C++11
- §6:概念
- §7:错误处理
- §8:C++17:大海迷航
- §9:C++20:方向之争
- §10:2020 年的 C++
- §11:回顾
如果一个主题跨越了一段较长的时间,比如“概念”和标准化流程,我会把它放在一个地方,让内容优先于时间顺序。
这篇论文特别长,真是一篇专题论文了。但是从 2006 年到 2020 年,C++ 经历了两次主要修订:C++11 和 C++20;而论文的早期读者们也都要求获得更多的信息。结果就是论文的页数几乎翻倍。即使以目前的篇幅,读者也会发现某些重要的主题没有得到充分的展现,如并发和标准库。
Footnotes
-
译注:即新的、非 C 风格的类型转换 ↩