C++ 2006–2020

 主页   资讯   文章   代码   电子书 

1. 前言

最初,我设计 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 标准委员会控制,它主要关注的是长长的新特性列表,以及对实际细节的关心。这是社区里最能表达和最有影响力的人所坚持的东西,仅仅基于哲学或理论观点就否认他们的关切和意见的话,恐怕就失之鲁莽了。

1.1 年表

为了给出一个快速的概述,这里有一个粗略的年表。如果你不熟悉 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]、booldynamic_cast
    • 库:STL(容器和算法)、stringbitset
  • 2011 年:C++11 [Becker 2011],TC++PL4 [Stroustrup 2013]
    • 语言:内存模型、auto、范围 forconstexpr、lambda 表达式、用户定义字面量……
    • 库:thread 和锁、futureunique_ptrshared_ptrarray、时间和时钟、随机数、无序容器(哈希表)……
  • 2014 年:C++14 [du Toit 2014]
    • 语言:泛型 lambda 表达式、constexpr 函数中的局部变量、数字分隔符……
    • 库:用户定义字面量……
  • 2017 年:C++17 [Smith 2017]
    • 语言:结构化绑定、变量模板、模板参数的构造函数推导……
    • 库:文件系统、scoped_lockshared_mutex(读写锁)、anyvariantoptionalstring_view、并行算法……
  • 2020 年:C++20 [Smith 2020]
    • 语言:conceptmodule、协程、三路比较、改进对编译期计算的支持……
    • 库:概念、范围、日期和时区、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)。

1.2 概述

这篇论文是按照 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;而论文的早期读者们也都要求获得更多的信息。结果就是论文的页数几乎翻倍。即使以目前的篇幅,读者也会发现某些重要的主题没有得到充分的展现,如并发和标准库。

[^1]: 译注:即新的、非 C 风格的类型转换