C++20:方向之争 C++20 特性 模块 协程 无栈协程 编译期 计算支持 静态反射 模式匹配

C++20:方向之争

C++20作为C++语言的一个新标准,带来了许多新的特性和改进,同时也引发了一些关于发展方向的争议。这些争议主要集中在如何平衡语言的复杂性、性能、易用性以及与其他编程语言和技术的集成等方面。

首先,C++20引入了一些新的语言特性和库,如concepts(概念)、ranges(范围)、coroutines(协程)等,这些特性旨在提高代码的可读性、可维护性和性能。然而,这些新特性的引入也增加了语言的复杂性,使得学习和掌握C++变得更加困难。因此,一些人认为C++20应该更加注重简化语言,减少不必要的复杂性,以便让更多的人能够轻松上手。

另一方面,C++一直以其高性能和底层访问能力而受到青睐。在C++20中,这些特性得到了进一步的加强,例如通过改进模板元编程和内存管理等方面。然而,这也引发了一些关于易用性和安全性的担忧。一些人认为,过于强调性能可能导致代码难以理解和维护,甚至可能引入安全隐患。因此,他们主张在C++20中更加注重易用性和安全性,以降低开发难度和风险。

此外,随着其他编程语言和技术的不断发展,如Rust、Go等,C++也面临着与其他语言竞争的压力。一些人认为,C++应该积极借鉴其他语言的优点,以便在竞争中保持领先地位。而另一些人则坚持C++的独特性和优势,认为保持其独特性才是发展的关键。

综上所述,C++20的方向之争主要围绕着复杂性、性能、易用性、安全性以及与其他语言的竞争等方面展开。这些争议并没有一个明确的答案,因为它们涉及到不同的开发需求、应用场景和个人偏好。因此,C++社区需要在这些方面做出权衡和选择,以便推动C++语言的持续发展。

由超过 35
0 名成员所组成的委员会来进行设计,不太可能产生一个连贯一致的结果。大家都有截然不同的背景(包括不同的教育背景),也都在各自的“日常工作”中承受不同的压力,自然会在方向上、优先级上和委员会程序上有不同的见解。粗略估算一下,对于每个提案,大概都有十多位成员会强烈反对其中部分内容。考虑到 WG21 希望同意人数达到 80% 或 90% 才宣告达成共识,C++ 到目前为止的成功令人惊讶。

9.1 设计原则

C++的设计原则主要涵盖了多个方面,旨在确保代码的可读性、可维护性、可扩展性和性能。以下是一些关键的设计原则:

单一职责原则(Single Responsibility Principle, SRP):每个类或模块应该只有一个引起它变化的原因。这意味着类或模块的功能应该被划分为单一的、独立的责任,以便于修改和扩展。

开放封闭原则(Open Closed Principle, OCP):软件实体(类、模块、函数等)应该对扩展开放,对修改封闭。这意味着当需要添加新功能时,应该通过添加新的代码来实现,而不是修改现有的代码。

里氏替换原则(Liskov Substitution Principle, LSP):子类必须能够替换其父类,并且在替换后,程序的行为应该保持不变。这强调了子类与父类之间的行为一致性。

接口隔离原则(Interface Segregation Principle, ISP):客户端不应该依赖于它不需要知道的接口。这意味着接口应该尽可能小和专一,以便客户端只需要了解它实际使用的部分。

依赖倒置原则(Dependency Inversion Principle, DIP):要依赖于抽象,不要依赖于具体。这意味着高层模块不应该依赖于低层模块,它们都应该依赖于抽象。抽象不应该依赖于细节,细节应该依赖于抽象。

除了上述SOLID原则外,C++的设计还遵循其他一些重要的理念:

面向对象:C++是一种面向对象的语言,支持多态、继承和封装等特性,这使得代码结构更加清晰、灵活和易于维护。

泛型编程:C++具有强大的泛型编程能力,通过模板函数和类的定义,可以提高代码的复用性和可扩展性。

资源管理:C++强调资源的有效管理,通过RAII(资源获取即初始化)和智能指针等技术,确保程序在退出前正确、安全地释放动态分配的资源。

在设计C++程序时,遵循这些原则可以确保代码的质量、可维护性和可扩展性,同时也有助于减少潜在的错误和缺陷。然而,需要注意的是,这些原则并不是孤立的,它们应该相互协作,共同指导程序的设计和实现。
C++ 想发展成什么样?或者说,WG2

1 对于它在努力做什么有一个清晰的观点么?我认为答案是否定的。每位成员对于这个问题都有想法,但没有一个想法既被广泛接受,同时又足够具体到可以指导实际的讨论和决策。

ISO C++ 标准委员会既没有一组得到广泛认可的设计标准,也没有一组得到广泛认可的采纳某个特性的标准。这并不是因为缺少这方面的尝试。我曾经反复不断地明确强调以下设计标准:

  • 在《C++ 语言的设计和演化》[Stroustrup 1994](§2.1)中提出的“经验法则”包括 RAII(§2.2.1)、面向对象编程、泛型编程和静态类型安全。
  • “简单的事情简单做!”(§4.2)则引出洋葱原则(§4.2)。
  • 从代码到硬件的直接映射和零开销抽象(§1)(§11.2)。
  • 基于意见反馈来发展 C++,以解决现实世界的实际问题(§11.2)。
  • 保持稳定性和兼容性 [Koenig and Stroustrup 1991b; Stroustrup 1994]。
  • 直接和硬件打交道的能力,强有力的可组合的抽象机制,以及最小化的运行时系统(参见我在 HOPL3 的论文 [Stroustrup 2007] 中的回顾)。

问题在于,人们发现要在解释上达成一致太难,而要忽视他们所不喜欢的又太容易。这种倾向,使得在“什么才是重要的”这个问题上的根本分歧得以发酵。大家基于他们所受的教育和他们的日常工作中所获得的理解,来做出设计决策。这种背景上的多样性,再加上标准委员会内部对于 C++ 广泛应用领域的不均衡覆盖(§3.3),就构成了一个问题。许多人只是对于自己的观点过于确定无疑 [Stroustrup 2019b]。而要分辨清楚到底什么只是一时的流行,什么从长远来看才对 C++ 社区有帮助,确实很困难。通常来说,第一个提出的解决方案往往不是最好的那个。

人们很容易在细节中迷失而忽略了大局。人们很容易关注当前的问题而忘记长期目标(以十年计)。相反,委员会成员是如此专注于通用的原则和遥远的未来,以至于对迫在眉睫的实际问题视而不见。

在 2017 年,一群国家标准机构代表团的领导人 [van Winkel et al. 2017] 要求对 C++ 的方向性问题予以正式严肃的考量,在他们的敦促之下,WG21 建立了方向组(Direction Group,通常称之为 DG)以试图解决设计目标和方向的问题(§3.2)。DG 在 2018 年 发布了它的第一个广泛而详尽的声明 [Dawes et al. 2018],强调了要遵守明确清晰的原则、一致性,并鼓励用流程来确保这些。比如说:

我们从根本上需要:

  • 稳定性:有用的代码“存活”达数十年。
  • 不断演进:世界在不断变化,而 C++ 也需要不断改变以面对新的挑战。

这里有一种内在的张力。

DG 强调一致性有必要贯穿整个标准:

现如今,某些最为强大的设计技术融合了传统的面向对象编程方面、泛型编程方面、函数式编程方面以及一些传统的命令式编程技术。这种组合,而不是理论上的纯粹,才是理想的。

  • 提供在风格(语法和语义)和使用风格上一致的特性。

该要求适用于库、语言特性,以及这两者的组合

当然了,还有静态类型:

C++ 极其依赖于静态类型安全,以达成其表达能力、性能和安全性。理想的情况下应有

  • 完全的类型安全和资源安全(没有内存损坏和内存泄漏)

该要求可以在不增加额外开销的情况下达成,尤其是,不需要添加垃圾收集器,也不需要限制表达能力。

国家机构领导的要求 [van Winkel et al. 2017] 和 DG 的文档 [Dawes et al. 2018] 都强调了委员会成员需要了解 C++ 的历史,以确保一定程度的连续性。一个缺乏历史的组织无法对他们的设计内容保持一致性的观点。因此,HOPL 论文 [Stroustrup 1993, 2007] 和《C++ 语言的设计和演化》[Stroustrup 1994] 扮演了基石角色。

传统上,为符合 WG21 在 ISO 的章程,C++ 演化方面的工作主要都聚焦于语言和库的课题。然而,开发者不仅仅需要考虑语言:程序是工具链(§1)的产物。令人震惊的是,C++ 并没有关于动态链接库的标准,也没有标准化的构建系统。工具研究小组 SG15 在 2018 年成立,以尝试应对工具方面的形形色色的问题(§3.2)。

9.2 我的 C++17 清单

我一直努力鼓励委员会关注重要的改进——而不只去做那些容易完成和容易达成一致的事情——作为这个努力的一部分,我制定了一个清单,包含了我认为重要且适合引入 C++17 的内容及其理由:

  • 概念——它让我们可以精确描述泛型程序,并解决对于错误信息质量的广泛抱怨。
  • 模块——只要它可以显著地提高与宏的隔离并大大优化编译时间。
  • 范围库和其他关键 STL 组件对概念的使用——为主流用户改进错误信息质量和提高库规范的精确性(“STL2”)。
  • 统一调用语法——简化模板库的规范和使用。
  • 协程——应该非常快速而简单。
  • 网络库支持——基于 asio 库,如相应 TS 所描述。
  • 契约——不一定需要在 C++17 的库规范中使用。
  • SIMD 向量和并行算法。
  • 标准库词汇类型,比如 optionalvariantstring_viewarray_view
  • 一种在栈上提供数组(stack_array)的“魔法类型”,合理支持安全、便捷的使用。

在 2015 年 4 月份,在 Kansas 州 Lenexa 的 WG21 会议中,我在晚间会议上向一些有共鸣的观众展示了这个清单。然而,几乎没有人感受到足够的动力去根据这个清单调整工作焦点。这个清单后来“泄露”了出去,并且在网上引起了混乱的讨论,因此我不得不把它正式写出来 [Stroustrup 2015a]。

如果是在一个团结的委员会中,该清单上的每一项都应该已经准备好进入 C++17 了。实际上我认为,如果我们专注于这个列表,完成其中的大约一半提案还是可行的。然而我还是过于乐观了。我们唯一达成共识的也就只有关于标准库词汇类型的那一项。其中 array_view 被重命名为 span,成了 C++20(§9.3.8)的一部分。

幸运的是,列表上的大部分条目进入了 C++20。除了

  • 网络库(§8.8.1)——现在是个 TS [Wakely 2018]
  • 契约(§9.6.1)——差一点进入 C++20
  • 统一函数调用(§8.8.3
  • SIMD 向量——目前在一个 TS 中 [Hoberock 2019]
  • stack_array

这份列表带来了日程安排上的争论。鉴于概念的提案(§6.3.8)在 2016 年的失败看起来是不可避免了,我被询问——由整个委员会——是否我打算提议推迟标准的发布一到两年,来把概念加入到标准中,让标准变成 C++18 或者 C++19。我拒绝了,因为我认为可预见的发布周期对于整个社区而言更为重要,其重要性要超过某个单项的改进。而且,当时也无法确保一定会就该提案形成共识,再说一次日程延误很可能会造成更多的延误。如果一份提案被认为值得推迟标准发布,那么就会有人主张也有其他的提案同样值得标准发布的推迟。这样的逻辑使得 C++0x 变成了 C++11,哪怕当时曾有人希望是 C++06。

9.3 C++20 特性

WG21 将针对 C++20 的新提案的截止日期定为 2018 年 11 月,并在 2019 年 2 月会议之后宣布“特性冻结”。2020 年 2 月,在捷克共和国布拉格举行的一次会议上,技术投票结果为 79 比 0,一票弃权 [Smith 2020]。所有 15 个国家成员体的代表团团长均投了赞成票。官方标准将由 ISO 在 2020 年末发布。C++20 特性包括:

  • §6.4概念——对泛型代码的要求进行明确规定
  • §9.3.1模块——支持代码的模块化,使代码更卫生并改善编译时间
  • §9.3.2协程——无栈协程
  • §9.3.3编译期计算支持
  • §9.3.4<=>——三路比较运算符
  • §9.3.5范围——提供灵活的范围抽象的库
  • §9.3.6日期——提供日期类型、日历和时区的库
  • §9.3.8跨度——提供对数组进行高效和安全访问的库
  • §9.3.7格式化——提供类型安全的类似于 printf 的输出的库
  • §9.4并发改进——例如作用域线程和停止令牌
  • §9.5很多次要特性——例如 C99 风格的指派初始化器和使用字符串字面量作为模板参数

以下内容在 C++20 时尚未准备就绪,但可能会成为 C++23 的主要特性:

  • §8.8.1网络——网络库(sockets 等)
  • §9.6.2静态反射——根据周围程序生成代码的功能
  • 模式匹配——根据类型和对象值选择要执行的代码 [Murzin et al. 2019]

C++20 提供了一组反映 C++ 长期目标的特性,并解决了一些根本问题。例如,在 1994 年的《C++ 语言的设计和演化》[Stroustrup 1994] 一书中就提到了模块和概念,而协程在整个 1980 年代都是“带类的 C”和 C++ 的一部分。C++20 对 C++ 的影响将与 C++11 一样大。

不幸的是,C++20 没有对模块和协程提供标准库支持。这可能会成为一个严重的问题,但当时实在没有时间来准备并赶上 C++20 的时间要求。C++23 应该会提供所需的支持(§4.1.3)。

9.3.1 模块

在 C++ 程序中改进模块化是一个显然的需求。C++ 从 C 语言中继承了 #include 机制,它依赖于从头文件使用文本形式包含 C++ 源代码,这些头文件中包含了接口的文本定义。一个流行的头文件可以在大型程序的各个单独编译的部分中被 #include 数百次。基本问题是:

  • 不够卫生:一个头文件中的代码可能会影响同一翻译单元中包含的另一个 #include 中的代码的含义,因此 #include 并非顺序无关。宏是这里的一个主要问题,尽管不是唯一的问题。
  • 分离编译的不一致性:两个翻译单元中同一实体的声明可能不一致,但并非所有此类错误都被编译器或链接器捕获。
  • 编译次数过多:从源代码文本编译接口比较慢。从源代码文本反复地编译同一份接口非常慢。

自“开辟鸿蒙”而始,这已经众所周知(例如,参见《C++ 语言的设计和演化》[Stroustrup 1994] 第 18 章),但随着越来越多的信息被放入头文件(inline 函数、constexpr 函数,还有尤其是模板),这些问题在这些年里变得越来越严重。在 C++ 的早期,通常 10% 的文本来自头文件,但现在它更可能是 90% 甚至 99%。考虑下面的代码:

#include<iostream>

int main()
{
   
    std::cout << "Hello, World\n";
}

这段典型的代码有 70 个字符,但是在 #include 之后,它会产生 419909 个字符需要编译器来消化。尽管现代 C++ 编译器已有骄人的处理速度,但模块化问题已经迫在眉睫。

在委员会的鼓励下(并得到了我的支持),David Vandevoorde 在二十一世纪产出了一系列模块设计 [Vandevoorde 2007,2012],但进展非常缓慢。委员会的首要任务是完成 C++0x,而不是在模块上取得进展。David 主要靠自己奋斗,此外基本就只得到一些精神支持了。在 2012 年,Doug Gregor 从苹果提交了一个完全不同的模块系统设计 [Gregor 2012]。在 Clang 编译器基础设施中,这一设计已经针对 C 和 Objective C 实现 [Clang 2014]。它依赖于语言之外的文件映射指令,而不是 C++ 语言里的构件。该设计还强调了不需要对头文件进行修改。

在 2014 年,由 Gabriel Dos Reis 领导的微软团队成员根据他们的工作提出了一项提案 [Dos Reis et al. 2014]。从精神层面上讲,它更接近于 David Vandevoorde 的设计,而不是 Clang/苹果的提议,并且很大程度上是基于 Gabriel Dos Reis 和 Bjarne Stroustrup 在得州农工大学所做的关于 C++ 源代码的最优图表示的研究(于 2007 年发布并开源 [Dos Reis 2009; Dos Reis and Stroustrup 2009, 2011])。

这为在模块方面取得重大进展奠定了基础,但同时也为苹果/谷歌/Clang 方式(和实现)及微软方式(和实现)之间的一系列冲突埋下了伏笔。

为此一个模块研究小组被创建。3 年后,该小组主要基于 Gabriel Dos Reis 的设计 [Dos Reis 2018] 制订了 TS。

在 2017 年,然后在 2018 年又发生了一次,将 Modules TS 纳入 C++20 标准的建议受阻,就因为谷歌提出了不同的设计 [Smith 2018a,b]。争论的主要焦点是在 Gabriel Dos Reis 的设计中宏无法导出。谷歌的人认为这是一个致命缺陷,而 Gabriel Dos Reis(和我)认为这对于模块化至关重要 [Stroustrup 2018c]:

模块化是什么意思?顺序独立性:import X; import Y; 应该与 import Y; import X; 相同。换句话说,任何东西都不能隐式地从一个模块“泄漏”到另一个模块。这是 #include 文件的一个关键问题。#include 中的任何内容都会影响所有后续的 #include

我认为顺序独立性是“代码卫生”和性能的关键。通过坚持这种做法,Gabriel Dos Reis 的模块实现也比使用头文件在编译时间上得到了 10 倍量级的性能提升——即使在旧式编译中使用了预编译头文件也是如此。迎合传统头文件和宏的常规使用的方式很难做到这一点,因为需要将模块单元保持为允许宏替换(“标记汤”)的形式,而不是 C++ 逻辑实体的图。

经过精心设计的一系列折中,我们最终达成了一个被广泛接受的解决方案。这一多年努力的关键人物有 Richard Smith(谷歌)和 Gabriel Dos Reis(微软),以及 GCC 的模块实现者 Nathan Sidwell(Facebook),还有其他贡献者 [Dos Reis and Smith 2018a,b; Smith and Dos Reis 2018]。从 2018 年年中开始,大多数讨论都集中在需要精确规范的技术细节上,以确保实现之间的可移植性 [Sidwell 2018; Sidwell and Herring 2019]。

考虑如下代码所示的 C++20 模块的简单示例:

export module map_printer;  // 定义一个模块

import iostream;       // 使用 iostream
import<
  • 28
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

EwenWanW

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值