微软交流社区

 找回密码
 立即注册
搜索
热搜: 活动 交友 discuz
查看: 132|回复: 0

C++的历史和其兼容性

[复制链接]

3

主题

4

帖子

10

积分

新手上路

Rank: 1

积分
10
发表于 2023-1-15 15:50:15 | 显示全部楼层 |阅读模式
本文来自C++之父Bjanre Stroustrup《A Tour of C++》第二版的第16章。
原来打算自己翻译着,没想到机械工业出版社已经出版了:-)
<hr/>欲速则不达。
—— 屋大维,凯撒·奥古斯都

  • 历史

    • 大事年表;早期的C++; ISOC++标准;标准和编程风格; C++的使用

  • C++ 特性演化

    • C++11语言特性;C++14语言特性;C++17语言特性;C++11 标准库组件;C++14标准库组件;C++17 标准库组件;已弃用特性

  • C/C++兼容性

    • C和C++是兄弟;兼容性问题

  • 参考文献
  • 建议
16.1 历史

我发明了C++,制定了最初的定义,并完成了第一个实现。我选择并制定了C++的设计标准,设计了主要的语言特性,开发或帮助开发了早期标准库中的很多内容,并且25年来一直在C++标准委员会中负责处理扩展提案。
C++的设计目的是,为程序组织提供Simula的特性[Dahl, 1970], 同时为系统程序设计提供C的效率和灵活性[Kernighan, 1978]。Simula 是C++抽象机制的最初来源。类的概念(以及派生类和虛函数的概念)也是从Simula借鉴而来的。不过,模板和异常则是稍晚从别207]处得到灵感而引入 C++的。
讨论C++的演化,总是要针对它的使用来谈。我花了大量时间倾听用户的意见,搜集有经验的程序员的观点。特别是,我在AT&T贝尔实验室的同事在C++的第一个十年中对其成长贡献了重要力量。
本节是一个简单概览,不会试图讨论每个语言特性和库组件,而且也不会深入细节。更多的信息,特别是更多贡献者的名字,请查阅我在“ACM程序设计语言历史”大会上发表的两篇论文[Stroustrup, 1993] [Stroustrup,2007]和我的《Design and Evolution of C++》《C++语言的设计和演化》一书(人们熟知的“D&E”) [Stroustrup, 1994]。这些资料介绍了C++的设计和演化,记录了C++从其他程序设计语言受到的影响。
一些文档是作为ISO C++标准工作的一部分而编写的,其中大部分都可以在网上找到[WG21]。在我的常见问题解答(FAQ)中,我设法维护标准库设施与其提出者和改进者之间的关联[Stroustrup, 2010]。C++ 并非一个不露面的匿名委员会或是一个想象中的万能的“终身独裁者”的作品,而是千万名甘于奉献的、有经验的、辛勤工作的人的劳动结晶。
16.1.1 大事年表

创造C++的工作始于1979年秋天,当时的名字是“带类的C"。下面是简要的大事.
年表:

  • 1979 “带类的C”的工作开始。最初的特性集合包括类、派生类、公有/私有访问控制、构造函数和析构函数以及带实参检查的函数声明。最初的库支持非抢占的并发任务和随机数发生器。
  • 1984“带类的C” 被重新命名为C++。在那个时候,C++ 已经引入了虚函数、函数与运算符重载、引用以及I/O流和复数库。
  • 1985 C++ 第一个商业版本发布(10月14日)。标准库中包含了I/O流、复数和多任务(非抢占调度)。
  • 1985《 C++ Programming Language》《C++程序设计语言》出版(“TC++PL", 10月14日) [Stroustrup, 1986]。
  • 1989《C++ Reference Manual》《C++参考手册批注版》出版(“the ARM" )[Ellis,1989]。
  • 1991《 C++ Programming Language, Second Edition》《C++程序设计语言(第2版》出版[Stroustrup, 1991],提出了使用模板的泛型编程和基于异常的错误处理,包括通用的资源管理理念“资源管理即初始化”(RAII)。
  • 1997《C++ Programming Language, Third Edition》《C++程序设计语言(第3版)》出版[Stroustrup, 1997],引入了ISOC++标准,包括名字空间、dynamic_cast和模板的很多改进。标准库加入了标准库模板库(STL) 泛型容器和算法框架。
  • 1998 ISO C++标准发布[C++,1998]。
  • 2002标准的修订工作开始, 这个版本俗称C++0x。
  • 2003 ISO C++标准的一个“错误修正版”发布。一个C++技术报告引入了新的标准库组件,诸如正则表达式、无序容器(哈希表)和资源管理指针,这些内容后来成为C++11的一部分。
  • 2006 ISO C++性能技术报告发布,涉及代价、可预测性和技术问题,这些主要与嵌人式系统程序设计相关[C++ ,2004]。
  • 2011 ISO C++11标准发布[C++,2011]。它引入了统一初始化、移动语义、从初始值推断类型(auto)、范围for、可变参数模板、lambda表达式、类型别名、一种适合并发的内存模型以及其他很多特性。标准库增加了一一些组件,包括线程、锁机制和2003年技术报告中的大多数组件。
  • 2013第一个完整的C++11实现出现。2013《 C++ Programming Language》《C++程序设计语言(第4版)》出版,增加了C++11的新内容。
  • 2014 ISO C++14标准发布[C++,2014]。在C++11基础上补充了变量模板、数字分隔符、泛型lambda和一些标准库改进。 第一个C++14实现完成。
  • 2015 C++核心准则项目开始[Stroustrup,2015]。2015概念技术规范被批准。
  • 2017 ISO C++17标准发布[C++,2017],提供了多种新特性,包括求值顺序保证、结构化绑定、折叠表达式、文件系统库、并行算法以及variant和optional类型。第一个C++17实现完成。
  • 2017模块技术规范和范围技术规范被批准。
  • 2020 (计划) 发布ISO C++20标准。
在开发过程中,C++11 也被称为C++0x。就像其他大型项目中也会出现的情况一样,我们过于乐观地估计了完工日期。在快完工时,我们开玩笑说C++0x中的“x”表示十六进制,因此C++0x变成了C++0B。另一方面,委员会按时发布了C++14和C++17,主要的编译器提供商也及时提供了对应的新产品。
16.1.2 早期的C++

我最初设计和实现一种新语言的原因是,希望在多处理器间和局域网内(现在被称为多核与集群)部署UNIX内核的服务。为此,我需要准确指明系统划分为几部分以及它们之间如何通信,Simula 是写这类程序的理想语言[Dahl,1970],但它的性能不佳。我还需要直接处理硬件的能力和高性能并发编程机制,C很适合编写这类程序,但它对模块化和类型检查的支持很弱。我将Simula风格的类机制加入到C(经典的C)中,结果就得到了“带类的C",它的一些特性适合于编写具有最小时间和空间需求的程序,在一些大型项目的开发中,这些特性经受了严峻的考研。“带类的C"缺少运算符重载、引用、虚函数、模板异常以及很多很多特性[Stroustrup, 1982]。 C++ 第一次应用于研究机构之外是在1983年7月。
C++这个名字(发音为“see plus plus")是由Rick Mascitti在1983年夏天创造的,我们选用它来取代我创造的“带类的C”。这个名字体现了这种新语言的进化本质一它是从C演化而来的,其中“++" 是C语言的递增运算符。一个稍短的名字“C+”是一个语法错误,它也曾被用于命名另一种不相干的语言。熟悉C语义的内行可能会认为C++不如++C。新语言没有被命名为D的原因是,它是C的扩展,它并没有试图通过删除特性来解决存在的问题,另一个原因是已经有好几个自称C语言继任者的语言被命名为D了。C++这个名字还有另一个解释,请查阅[Orwell, 1949]的附录。
最初设计C++的目的之一是,让我的朋友和我不必再用汇编语言、C语言以及当时各种流行的语言编写程序。其主要目标是能让程序员更简单、更愉快地编写好程序。在最初,C++并没有“图纸设计”阶段,其设计、文档编写和实现都是同时进行的。当时既没有“C++项目",也没有“C++ 设计委员会”。自始至终,C++ 的演化都是为了处理用户遇到的问题,主导演化的主要是我的朋友、同事和我之间的讨论。
C++最初的设计(当时还叫“带类的C" )包含带参数类型检查和隐式类型转换的函数声明、具备接口和实现间public/private差异的类机制、派生类以及构造函数和析构函数。我使用宏实现了原始的参数化机制,并一直沿用至1980年代中期。当年年底,我提出了一组语言设施来支持一套完整的程序设计风格。回顾往事,我认为引人构造函数和析构函数是最重要的。用当时的术语来说[Stroustrup,1979]:
一个“创建函数”为成员函数创建执行环境,而“删除函数”则完成相反的工作。
不久之后,“创建函数”和“删除函数”被重命名为“构造函数”和“析构函数”。这是C++资源管理策略的根(导致了对异常的需求),也是许多让用户代码更简洁清晰的技术的关键。我没有听说过(到现在也没有)当时有其他语言支持能执行普通代码的多重构造函数。而析构函数则是C++新发明的特性。
C++第一个商业化版本发布于1985年10月。到那时为止,我已经增加了内联、const、函数重载、引用、运算符重载和虚函数等特性。在这些特性中,以虚函数的形式持运行时多态在当时是最受争议的。我是从Simula中认识到其价值的,但我发现几乎不可能说服大多数系统程序员也认识到它的价值。系统程序员总是对间接函数调用抱有怀疑,而熟悉其他支持面向对象编程的语言的人则很难相信virtual函数能快到足以用于系统级代码中。与之相对,很多有面向对象编程背景的程序员在当时很难习惯(现在很多人仍不习惯)这样一个理念:你使用虚函数调用只是为了表达一个必须在运行时做出的选择。虚函数当时受到很大阻力,可能与另一个理念也遇到阻力相关:你可以通过一.种程序设计语言所支持的更正规的代码结构来实现更好的系统。因为当时很多C程序员似乎已经接受:真正重要的是彻底的灵活性和程序的每个细节都仔细地人工打造。而当时我的观点是(现在也是):我们从语言和工具获得的每一点帮助都很重要,我们正在创建的系统的内在复杂性总是处于我们能(否)表达的边缘。
早期的文档(如[Stroustrup,1985]和[Stroustrup,1994])这样描述C++:
C++是这样一个通用编程语言:

  • 它是更好的C
  • 它支持数据抽象
  • 它支持面向对象编程
注意,并没有“C++ 是一种面向对象编程语言”。其中,“支持数据抽象”指的是信息隐藏、非类层次中的类和泛型编程。在最初,对泛型编程的支持很蹩脚——是通过使用宏来实现的[Stroustrup,1981]。模板和概念(concepts)则是很久以后才出现的。
C++的很多设计都是在我的同事的黑板上完成的。在早期,Stu Feldman、Alexander Fraser、Steve Johnson、Brian Kernighan、Doug Mellroy和Dennis Ritchie都给出了宝贵的意见。
在20世纪80年代的后半段,作为对用户反馈的回应,我继续添加新的语育特性。其中最重要的是模板[Stroutrup, 1988]和异常处理[Koenig, 1990], 在标准制定工作开始时,这两个特性还都处于实验性状态。在设计模板的过程中,我被迫在灵活性、效率和提早类型检查之间做出决断。在那时,没人知道如何同时实现这三点。为了在高要求的系统应用开发方面能与C风格代码竞争,我觉得应该选择前两个性质。回顾往事,我认为这个选择是正确的,模板类型检查尚未有完善的方案,对它的探索一直在进行中[DosReis, 2006][Gregor, 2006][Sutton, 2011 ][Stroustrup, 2012a]。异常的设计则关注异常的多级传播、将任意信息传递给一个异常处理程序以及异常和资源管理的融合。最后一点的解决方案是使用带析构函数的局部对象来表示和释放资源,我笨拙地将这种关键技术命名为“资源获取即初始化”( Resource Acquisition Is Initialization), 其他人很快将其简化为首字母缩写RAII。
我推广了C++的继承机制,使之支持多重基类[Stroustrup, 1987a]。 这种机制被称为多重继承(multiple inheritance),它被认为是很有难度的且有争议的。我认为它远不如模板和异常重要。当前,支持静态类型检查和面向对象编程的语育普遍支持抽象类(通常被称为接口(interface)) 的多重继承。
这些目标在[Stroustrup, 2007]中有记载和详细介绍。C++11标准制定的一项主要工作是实现并发系统程序设计的类型安全和可移植性。这包括一个内存模型和一组无锁编程特性,这些工作主要是由Hans Boehm、Brian McKnight和其他一些人完成的。在此基础上,我们添加了`thread库。在C++11之后,大家一致认为间隔13年才推出新标准过于漫长了。Herb Sutter提议委员会采取“火车模型",即按固定时间间隔发布标准的策略。我强烈主张缩短间隔来降低延期的可能,因为有些人坚持更多时间只是为了多加入“一个基本特性”。我们一致决定采用雄心勃勃的3年时间表,并采用次要和主要版本交替的方式。ula风格的仿真。不幸的是,一直等到2011年(已经过去了30年了! ),并发特性才被放进标准并被C++实现普遍支持(参见第15章)。协同程序似乎成了C++20的一部分。模板设施的发展受到了vector、map、list和sort等各种模板的影响,这些模板是由Andrew Koenig、Alex Stepanov、我以及其他一些人设计的。`
1998年标准库中最重要的革新是STL的引入,这是标准库中一个算法和容器的框架。它是Alex Stepanov (与Dave Musser. Meng Lee及其他一些人)设计的,来源于超过10年的泛型编程的相关工作。STL 已经在C++社区和更大范围内产生了巨大影响。
C++的成长环境中有着众多成熟的和实验性的程序设计语言(例如Ada [Ichbiah, 1979]、Algol 68 [Woodward, 1974]和ML [Paulson, 1996])。那时,我畅游在大约25种语言之中,它们对C++的影响都记录在[Stroustrup, 1994]和[Stroustrup, 2007]中。但是,决定性的影响总是来自于我遇到的应用。这是一个深思熟虑的策略,它令C++的发展是“问题驱动"的,而非简单模仿。
16.1.3 ISO C++标准

C++的使用爆炸式增长,这导致了一些变化。1987 年的某个时候,事情变得明朗,C++的正式标准化已是必然,我们必须开始为标准化工作做好准备[Stroustrup, 19941。 因此,我们有意识地保持C++编译器实现者和主要用户之间的联系——通过文件、 通过电子邮件以及C++大会上和其他场合下的面对面会议。
AT&T贝尔实验室允许我与C++实现者和用户共享C++参考手册修订版本的草案,这对C++及其社区做出了重要贡献。由于这些实现者和用户中很多人都供职于可视为AT&T竞争者的公司中,这一贡献的重要性绝对不应被低估。一个不甚开明的公司可能不会这样做,从而导致严重的语言碎片化问题。正是由于AT&T这样做了,使得来自数十个机构的大约100人阅读了草案并提出了意见,使之成为被普遍接受的参考手册和ANSI C++标准化工作的基础文献。这些人的名字可以在《The Annotated C++ Reference Manual》(C++参考手册批注版) (“the ARM”) [Ellis, 1989]中找到。ANSI 的X3J16委员会于1989年12月筹建,是由HP公司发起的。1991 年6月,这一ANSI (美国国家) C++标准化工作成为ISO(国际)C++标准化工作的一部分,并被命名为WG21。自1990年起,这些联合的标准委员会逐渐成为C++语言演化及其定义完善工作的主要论坛。我自始至终在这些委员会中任职。
特别是,从1990年至2014年,作为扩展工作组(后来改称演化工作组)的主席,我直接负责处理C++重大变化和新特性加入的提案。最初标准草案的公众预览版于1995年4月发布。1998年, 第一个ISO C++标准( ISO/IEC 14882--1998) [C++, 1998] 被批准,投票结果是22个国家赞成0个国家反对。此标准的“错误修正版”于2003年发布,因此你有时会听人提到C++03,但它与C++98本质上是相同的语言。
C++11曾经多年被称为C++0x,它是WG21的成员的工作成果。委员会的工作流程和程序日益繁重,但这都是自愿增加的。这些流程可能导致更好的(也更严格的)规范,但也限制了创新[Stroustrup, 2007]。这一版标准最初草案的公众预览版于2009年发布,正式的ISO C++标准( ISO/IEC 14882--2011 ) [C++, 2011]于2011年8月被批准,投票结果是21票赞成,0票反对。
造成两个版本之间漫长的时间间隔的原因是,大多数委员会成员(包括我)都对ISO的规则有一个错误印象,以为在一个标准发布之后,在开始新特性的标准化工作之前要有一个“等待期”。结果造成新语言特性的重要工作2002年才开始。其他原因包括现代语言及其基础库8益增长的规模。以标准文本的页数来衡量,语言的规模增长了30%,而标准库则增长了100%。规模的增长大部分都是由更加详细的规范而非新功能造成的。而且,新C++标准的工作显然要非常小心,不能产生不兼容而导致旧代码产生问题。委员会不可以破坏数十亿行正在使用的C++代码。保持数十年的稳定性是一项至关重要的 “特性”。
C++11向标准库增加了很多设施并推动了语言特性集合的完善,这都是一种综合编程风格的需求——在 C++98中已被证明是很成功的“范型”和风格的综合。
C++11标准制定工作的总体目标是:

  • 使C++成为系统程序设计和构造库的更好的语言。
  • 使C++更容易教和学。
这些目标在[Stroustrup, 2007]中有记载和详细介绍。C++11标准制定的一项主要工作是实现并发系统程序设计的类型安全和可移植性。这包括一个内存模型(memory model)和一组无锁编程(lock-free programming)特性,这些工作主要是由Hans Boehm、Brian McKnight和其他一些人完成的。在此基础上,我们添加了thread库。在C++11之后,大家一致认为间隔13年才推出新标准过于漫长了。Herb Sutter提议委员会采取“火车模型",即按固定时间间隔发布标准的策略。我强烈主张缩短间隔来降低延期的可能,因为有些人坚持更多时间只是为了多加入“一个基本特性”。我们一致决定采用雄心勃勃的3年时间表,并采用次要和主要版本交替的方式。
C++14的初衷就是一个次要版本,目标是“完善 C++11”。这反映了现实情况,当发布日期确定后,总是有一些特性我们明确想要,但不能按时发布。而且,一旦被广泛使用,特性集之间的差异不可避免地会被发现。这些都适合在次要版本中完善。为了能令标准化工作进展得更快,为了能并行开发独立的特性,以及为了能更好地利用
很多志愿者的热情和能力,委员会利用了ISO“技术规范”( Technical Specification, TS)的开发和发布机制。这种机制看起来很适合标准库组件,虽然它可能导致开发过程中更多的阶段,从而导致延期。对于语言特性,TS机制看起来就不那么奏效了。一个可能的原因是,很少有重要的语言特性是真正独立的,毕竟标准和TS在文字工作方面并没有什么不同,而且毕竟很少有人会对编译器实现进行实验。
C++17则是一个主要版本。我认为“主要”的含义是,这个版本所包含的特性会改变我们思考软件设计和结构的方式。从这个角度看,C++17 最多是一个中间版本。它包含了很多小的扩展,但能带来巨大变革的特性(如概念(concepts)、模块(modules)和协同(coroutines)程序)要么还未准备好,要么陷入了争论中、缺乏设计方向。因此,C++17包含一些适合每个人的新特性, 但对于那些已经从C++11和C++14吸收了很多知识的程序员来说,没有能令他们的生活发生显著改变的新东西。我希望C++20能按承诺成为急需的主要版本,那些重要的新特性在2020年之前能被编译器广泛支持。面临的风险是“委员会设计”、特性膨胀、缺乏-致风格 以及短视的决策。在一个每次会议都有超过100个成员出席(还有更多人在线参加)的委员会中,这种不良现象几乎是不可避免的。向着更易使用、更一致的语言前进是非常困难的。
16.1.4 标准和编程风格

一个标准描述了什么可以正确运作以及它们是如何运作的,但不会描述如何良好、有效地使用它们。能很好地理解编程语言特性的技术细节并不意味着能有效地将它们与其他特性、库和工具结合使用来构造更好的软件,两者之间存在巨大差异。“更好”的含义是“更易维护、更不易出错以及更快”。我们需要开发、普及并支持一致的编程风格。而且,我们必须为旧式代码向着这些更现代、更高效、更一致的风格进化提供支持。
随着语言和标准库的发展,普及有效编程风格的问题变得非常重要。很多程序员目前所采用的编程风格对某些任务很有效,让这么大的一个群体抛弃当前编程风格异常困难。现在还有人将C++当作C的一些微小补充,也还有人认为基于大量类层次的20世纪80年代的面向对象的编程风格是程序开发的顶峰。有很多人挣扎于在充斥大量旧式C++代码的环境中如何用好C++11。另一方面,也有很多人满腔热情地过度使用新特性。例如,有些程序员
坚信只有使用了大量模板元编程的代码才是真正的C++。
什么是现代C++?在2015年,我开始着手设计一套以清晰缜密的基本原理支撑的编码指南,以期回答这个问题。很快我就发现我不是一个人在努力克服这个问题,而是在与来自世界很多地方的人们一起做这件事,特别是来自微软、红帽和脸书的技术人员,我们开始了“C++ 核心准则”(C++ Core Guidelines)项目[Stroustrup,2015]。这是一个很有野心的项目,目标是为设计更简单、更快且更易维护的代码打下完善的类型安全和资源安全基础[Stroustrup,2016]。除了基本原理基础上的详细编码规则外,我们还开发了静态分析工具和一
个小型的支持库作为这部指南的支撑。我认为,对于推动C++社区大规模地向着新的语言特性、库和支持工具前进,以期从它们的改进中受益这个目标而言,上述工作是至关重要的。
16.1.5 C++的应用

现在,C++是一种应用非常广泛的编程语言。其用户数从1979年的一个人快速增长到1991年的大约400 000人。即,在十多年的时间内,用户数一直保持大约每7.5个月翻一番。自然,在初期的急剧增长之后,增长率放缓下来,但据我乐观估计,到2018年,世界上大约有450,000,00 C++程序员[Kazakova,2015]。其中大部分增长发生在2005年之后,随着处理器速度指数爆发式增长停滞,语言性能的重要性突显。而且,这种增长并非源自正式的市场营销或有组织的用户社区推动。
C++主要是一种工业语言。 即,相比于在教育或程序设计语言研究领域,它在工业界更为突出。它成长于贝尔实验室,受到电信和系统编程(包括设备驱动、网络和嵌入式系统)各式各样迫切需求的激发。从那里,C++的应用漫延到每个工业领域:微电子、网络应用和基础设施、操作系统、金融、医疗、汽车、航空航天、高能物理、生物、能源生产、机器学习、视频游戏、图形学、动画、虛拟现实以及其他更多领域。它的主要应用领域都是需要C++结合有效利用硬件和管理复杂性的能力来解决问题。而且,看起来应用领域还在不断扩张[Stroustrup,1993] [Stroustrup,2014]。
16.2 C++ 特性演化

在本节中,我列出C++11、C++14和C++17新增的语言特性和标准库组件。
16.2.1 C++11 语言特性

查看语言特性列表很容易让人感到困惑。你需要记住的是,语言特性不是单独使用的。
特别是,大多数C++11新特性如果离开了旧特性提供的框架都毫无意义。
[1] 用{}列表进行统一、通用的初始化
[2] 从初始值进行类型推断:auto
[3] 防止类型窄化(Type narrowing)
[4] 泛化的、有保证的常量表达式:constexpr
[5] 范围for语句
[6] 空指针关键字:nullptr
[7] 有作用域的且强类型的enum:enum class
[8] 编译时断言: static_assert
[9] {}列表到std::initializer_list的语言层的映射
[10] 右值引用,移动语义
[11] 以>>(两个>之间没有空格)结束的嵌套模板参数
[12] lambda表达式
[13] 可变参数模板
[14] 类型和模板别名
[15] 万国码(Unicode)字符
[16] long long整数类型
[17] 对齐控制:alignas和alignof
[18] 在声明中将一个表达式的类型作为类型使用的能力:decltype
[19] 原始字符串字面值(raw string literal)
[20] 泛化的POD (Plain old Data,简单旧数据)
[21] 泛化的union
[22] 局部类作 为模板参数
[23] 后缀返 回类型语法
[24] 一种属性(attribute)语法和两种标准属性:[[carries_ dependency]]和[[noreturn]]
[25] 防止异常传播:noexcept说明符
[26] 在表达式中检测throw的可能性:noexcept运算符
[27] C99特性:扩展的整型类型(即,可选的长整数类型的规则);窄/宽字符串的连接;__STDC_HOSTED__;_Pragma(X);可变参数宏和空宏参数
[28] 名为__func__的字符串保存当前函数的名字
[29] inline命名空间
[30] 委托构造函数(Delegating Constructor)
[31] 类内成员初始值(In-class member initializers)
[32] 类内默认控制:default和delete
[33] 显式转换运算符
[34] 用户自定义字面值
[35] template实例化的更显式的控制:extern template
[36 ]函数模板的默认模板参数
[37] 继承构造函数
[38] 覆盖控制:override和final
[39] 更简单更通用的SFINAE(Substitution Failure Is Not an Error)规则
[40] 内存模型
[41] 线程局部存储:thread_local`
有关C++98到C++11变化的更完整的介绍,请参阅[Stroustrup,2013]。
16.2.2 C++14 语言特性

[1] 函数返回类型推断
[2] 改进的constexpr函数,如允许for循环
[3] 变量模板
[4] 二进制字面值
[5] 数字分隔符
[6] 泛型lambda
[7] 更通用的lambda捕获
[8] [[deprecated]]属性
[9] 其他一些微小扩展
16.2.3 C++17 语言特性

[1] 有保证的复制消除(Guaranteed copy elision)
[2] 动态分配过度对齐类型(Dynamic allocation of over-aligned types)
[3] 更严格的求值顺序
[4] UTF-8字面值(u8)
[5] 十六进制浮点数字面值
[6] 折叠表达式(Fold Expression)
[7] 泛型值模板参数(auto模板参数)
[8] 类模板参数类型推断
[9] 编译时if(即if constexpr)
[10] 带初始值的选择语句(Selection statements with initializers,即if/switch statement with initializer)
[11] constexpr lambda
[12] inline变量
[13] 结构化绑定(Structured Bindings)
[14] 新标准属性:[[fallthrough]]、 [[nodiscard]]和[[maybe_unused]]
[15] std::byte类型
[16] 用其基础类型的值初始化enum
[17] 其他一些微小扩展
16.2.4 C++11 标准库组件

C++11以两种形式向标准库添加新内容:全新组件(如正则表达式匹配库)和改进C++98组件(如容器的移动构造函数)。
[1] 容器的initializer_list构造函数
[2] 容器的移动语义
[3] 单向链表:forward_list
[4] 哈希容器:unordered_map、unordered_multimap、unordered_set和unordered_multiset
[5] 资源管理指针:unique_ptr、 shared_ptr和weak_ptr
[6] 并发支持: thread、互斥对象(mutex)、锁(lock)和条件变量(condition variables)
[7] 高层并发支持:packaged_thread、 future、promise和async()
[8] tuple
[9] 正则表达式:regex
[10] 随机数:分布和引擎
[11] 整数类型名,如int16_t,uint32_t和int_fast64_t
[12] 定长且连续存储的序列容器:array
[13] 拷贝和重抛出异常
[14] 用错误码报告错误:system_error
[15] 容器的emplace()操作
[16] constexpr函数更广泛的应用
[17] noexcept函数的系统使用
[18] 改进的函数适配器:function和bind()
[19] string到数值的转换
[20] 有作用域的分配器(Scoped allocators)
[21] 类型萃取(Type traits),如is_integral和is_base_of
[22] 时间工具:duration和time_point
[23] 编译时有理数运算:ratio
[24] 结束一个进程:quick_exit
[25] 更多算法,如move()、copy_if()和is_sorted()
[26] 垃圾回收 ABI
[27] 底层并发支持:atomic
16.2.5 C++14 标准库组件

[1] shared_mutex
[2] 用户自定义字面值(User-defined literals)
[3] 按类型元组寻址(Tuple addressing by type)
[4] 关联容器异构查找(Associative container heterogenous lookup)
[5] 其他一些次要特性
16.2.6 C++17 标准库组件

[1] 文件系统(File system)
[2] 并行算法
[3] 特殊数学函数
[4] string_view
[5] any
[6] variant
[7] optional
[8] invoke()
[9] 基本字符串转换:to_chars和from_chars
[10] 多态分配器(Polymorphic allocator)
[11] 其他一些次要特性
16.2.7 已弃用特性

目前,有数十亿行C++代码“在那里",而且没有人确切地知道哪些特性用于关键应用中。因此,ISO委员会只是无奈地启用旧的特性,而且会经过若干年的警告期。但是,有时弃用的是一些麻烦特性:

  • C++17最终弃用了异常说明:
void f() throw(x,Y); // C++98 异常说明,现在是error
支持异常说明的一些设施unexcepted_handler、set_unexpected()、get_unexpected()和unexpected()也被弃用了。应替代使用noexcept。

  • 不再支持三字母词(Trigraphs)。
  • auto_ptr被弃用。应替代使用unique_ptr。
  • 存储说明符register被弃用。
  • bool类型的++运算符被弃用。
  • C++98的export特性被弃用,因为它太复杂,主要的编译器提供商都未支持它。取而代之,export被用作模块(module)相关的一个关键字。
  • 如果一个类有析构函数,为其生成拷贝构造函数和拷贝赋值运算符的特性被弃用了。
  • 不再允许将字符串字面值赋予一个char *。 应替代使用const char *或auto。
  • 一些C++标准库函数对象和相关函数被弃用了,其中大多数是与参数绑定(argument binding)相关的。应替代使用lambda表达式和function。
通过弃用一个特性,标准委员会表达了希望程序员不再使用该特性的愿望。但是,委员会没有权利立刻删除一个广 泛使用的特性——即使该特性可能是冗余的或是危险的。因此,委员会通过“弃用"这样一个强烈暗示,提示程序员这个特性在将来的标准中可能消失,应避免使用。如果程序员继续使用弃用的特性,编译器可能给出警告。但是,已弃用的特性仍是标准的一部分,而且历史表明,出于兼容性考虑,这些特性其实会“永远”保留。
16.3 C/C++兼容性

除了少数例外,C++ 可以看作C (这里指C11标准,参见[C11])的超集。两者的不同大部分源于C++更为强调类型检查。一个编写得很好的C程序往往也会是一个合法的C++程序。主流编译器可以诊断出C++和C之间的所有不同。C++标准的附录C中列出了C99和C++11之间的不兼容之处。
16.3.1 C和C++ 是兄弟

经典C有两个主要后代: ISOC 和ISO C++。多年以来,两种语言在以不同的步调,沿着不同的方向发展着。造成的一个结果就是它们都支持传统C风格编程,但支持的方式有着细微不同。所产生的不兼容会使某些人非常苦恼一同时使用C 和C++的人、使用一种语言编写程序但用到另一种语言编写的库的人以及为C和C++编写库和工具的人。
我为何会说C和C++是兄弟呢?毕竞C++很明显是C的后代。但是,请看下面简化后的家谱。


在此图中,实线表示大量特性的继承,短杠虚线表示主要特性的借用,而点虚线表示次要特性的借用。从中可以看出,ISO C和ISO C++是K&R C [Kernighan,1978] 的两个主要后代,因此它们是兄弟。两者的发展过程中都从经典C继承了关键特性,但又都不是100%兼容经典C。“经典C"十词是我从Dennis Ritchic 的显示器上贴的便条中挑出来的。它大致相当于K&R C加上枚举和struct赋值两个特性。BCPL是在[Richards,1980]中定义的,
C89是在[C90]中定义的。
注意,C和C++的差别并不一定是C++演化过程中对C特性做出改变的结果。有很多不兼容的例子是在将C++中已存在很久的特性引入C时产生的。例如,T * 到void * 的赋值以及全局const的链接[Stroustrup, 2002]。有时,一个特性都已经成为ISO C++标准的一部分,才被引入C并产生了不兼容,例如inline的含义。
16.3.2 兼容性问题

C和C++有很多小的不兼容之处。所有这些不兼容都能给程序员带来麻烦,但也都可以在C++中解决。如果没有其他不可解决的不兼容问题,C代码片段可以作为C程序编译并使用extern "C"机制与C++程序链接到一起。
将一个C程序转换为C++程序可能遇到的主要问题有:

  • 次优的设计和编程风格。
  • 将一个void *隐式转换为一个T *(即,没有使用显式类型转换)。
  • 在C代码中将C++关键字用作了标识符,如class和private。
  • 作为C程序编译的代码片段和作为C++程序编译的代码片段链接时不兼容。
16.3.2.1 风格问题

C程序自然按C风格来编写,例如K&R风格[Kernighan, 1988]。 这意味着到处使用指针和数组,可能还有大量的宏。用这些设施编写大型程序,很难做到可靠。还有,资源管理和错误处理代码通常是为特定程序专门编写的;通过文档说明(而不是语言和工具所支持的),而且文档往往不完整,代码的依附性也太强。将一个C程序简单地逐行转换为一个C++程序,对得到的程序最好进行全面检查。实际上,我将C程序改写为C++程序从来没有无错的。这种改写工作,如果不改变基础结构,那么根本的错误来源也就仍然存在。如果原始的C程序中就有不完整的错误处理、资源泄漏或是缓存溢出,那么在C++版本中它们还会存在。为了获得大的收益,你必须改变代码的基础结构:
(1) 不要将C++看作增加了一些特性的C。你可以这样来使用C++,但这将导致次最优的结果。为了真正发挥C++相对于C的优势,你需要采用不同的设计和实现风格。
(2) 将C++标准库作为学习新技术和新程序设计风格的老师。注意它与C标准库的差异(例如,字符串拷贝用=而不是strcpy(),以及字符串比较用==而不是strcmp())。
(3) C++几乎从不需要宏替换。作为替代,使用const、constexpr、enum或enum class来定义明示常量,使用inline来避免函数调用开销,使用template来指明函数族或类型族,使用namespace来避免命名冲突。
(4) 在真正需要一个变量时再声明它,且声明后立即进行初始化。声明可以出现在语句可能出现的任何位置,包括for语句初始值部分和条件中。
(5) 不要使用malloc()。new运算符可以完成相同的工作,而且完成得更好。同样,不要使用realloc(),尝试用vector。但注意不要简单地用“裸的”new和delete来代替malloc()和free()。
(6) 避免使用void*、联合(union)以及类型转换,除非在某些函数和类的深层实现中。使用这些特性会限制你从类型系统得到的支持,而且会损害性能。在大多数情况下,一次类型转换就暗示着一个设计错误。
(7) 如果你必须使用显式类型转换,尝试使用命名转换(named cast,如static_cast),这能更精确地表达你的意图。
(8) 尽量减少数组和C风格字符串的使用。与这种传统的C风格程序相比,通常可以用C++标准库中的string、array和vector写出更简单也更易维护的代码。一般而言,如果标准库中已经提供了相应的功能,就尽量不要自己重新构造代码。
(9) 除非是在非常专门的代码中(例如内存管理器),或是进行简单的数组遍历(例如++p),否则要避免对指针进行算术运算。
(10) 不要认为用C风格(回避诸如类、模板和异常等C++特性)辛苦写出的程序会比一个简短的替代程序(例如,使用标准库特性写出的代码)更高效。实际情况通常(当然并不是绝对的)正好相反。
16.3.2.2 void *

在C中,void*可用来为任何指针类型的变量赋值或初始化,但在C++中则可能是不行的。例如:
void f(int n) {
        int* p = malloc(n * sizeof(int)); /* 不是C++代码; 在C++中,用"new"分配 */
// ...
}
这可能是最难处理的不兼容问题了。注意,从void *到不同指针类型的转换并非总是无害的:
char ch;
void* pv = &ch;
int* pi= pv; //C++不可以
*pi= 666; // 覆盖了ch和临近字节中的数据
如果你同时使用两种语言,应将malloc()的结果转换为正确类型。如果你只使用C++,应避免使用malloc()。
16.3.2.3 链接

C和C++可以实现为使用不同的链接规范(通常很多实现也确实这么做)。其基本原因是C++更为强调类型检查。还有一个实现上的原因是C++支持重载,因此可能出现两个都叫作open()的全局函数,链接器必须用某种办法解决这个问题。
为了让一个C++函数使用C链接规范(从而使它可以被C程序片段所调用),或者反过来,让一个C函数能被C++程序片段所调用,需要将其声明为extern "C"。例如:
extern "C" double sqrt(double);
这样,sqrt(double)就可以被C或C++代码片段调用,而其定义既可以作为C函数编译也可以作为C++函数编译。
在一个作用域中,对于一个给定的名字,只允许一个具有 该名字的函数使用C链接规范(因为C不允许函数重载)。链接说明不会影响类型检查,因此对一个声明为extern"C"的函数仍要应用C++函数调用和参数检查规则。
16.4 参考文献


16.5 建议

[1] ISOC++标准[C++, 2017]定义了C++。
[2] 当为一个新项目选择一种风格时,或是对一个代码库进行现代化时,依靠C++核心准则。
[3] 当学习C++时,不要孤立地关注单个语言特性。
[4] 不要陷人几十年之久的古老的语言特性集和设计技术中。
[5] 在产品级代码中使用新特性之前,先进行试验,编写一些小程序,测试你计划使用的C++实现与标准是否一致, 性能是否满足要求。
[6] 学习C++时,使用你能得到的最新的、最完整的标准C++实现。
[7] C和C++的公共子集并非学习C++的最好起点。
[8] 优先选择命名类型转换,如static_cast, 而非C风格类型转换。
[9] 当你将一个C程序改写为C++程序时,首先检查函数声明(原型)和标准头文件的使用是否一致。
[10] 当你将一个C程序改写为C++程序时,重新命名与C++关键字同名的变量。
[11] 出于移植性和类型安全的考虑,如果你必须使用C,应使用C和C++的公共子集编写程序。
[12] 当你将一个C程序改写为C++程序时,将malloc()的结果转换为恰当的类型,或者索性将所有malloc()都改为new。
[13] 当你用new和delete替换malloc()和free()时,考虑改用vector.push_back()和reserve()而不是realloc()。
[14] C++不允许int到枚举类型的隐式类型转换,如果必须进行这种转换,使用显式类型转换。
[15] 每个标准C头文件<x.h>都将名字定义在全局名字空间中,对应的C++头文件<cX>则将名字定义在名字空间std中。
[16] 声明C函数时使用extern "C"。
[17] 优先选择string而不是C风格字符串(直接处理以0结尾的char数组)。
[18] 优先选择iostream而不是stdio。
[19] 优先选择容器(如vector)而不是内置数组。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|小黑屋|微软交流社区

GMT+8, 2025-1-7 04:42 , Processed in 0.078383 second(s), 19 queries .

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表