《深入理解C++11:C++ 11新特性解析与应用》——1.4 C++特性一览

简介: 本节书摘来自华章计算机《深入理解C++11:C++ 11新特性解析与应用》一书中的第1章,第1.4节,作者 IBM XL编译器中国开发团队,更多章节内容可以访问云栖社区“华章计算机”公众号查看。

1.4 C++特性一览

接下来,我们会一窥C++11中的各种特性,了解它们的来历、用途、特色等。可能这部分对于还没有开始阅读正文的读者来说有些困难。如果有机会,我们建议读者在读完全书后再回到这里,这也是全书最好的总结。

1.4.1 稳定性与兼容性之间的抉择

通常在语言设计中,不破坏现有的用户代码和增加新的能力,这二者是需要同时兼顾的。就像之前的C一样,如今C++在各种代码中、开源库中,或用户的硬盘中都拥有上亿行代码,那么当C++标准委员会要改变一个关键字的意义,或者发明一个新的关键字时,原有代码就很可能发生问题。因为有些代码可能已经把要加入的这个准关键字用作了变量或函数的名字。

语言的设计者或许能够完全不考虑兼容性,但说实话这是个丑陋的做法,因为来自习惯的力量总是超乎人的想象。因此C++11只是在非常必要的情况下才引入新的关键字。WG21 在加入这些关键字的时候非常谨慎,至少从谷歌代码搜索(Google Code Search)的结果看来,这些关键字没有被现有的开源代码频繁地使用。不过谷歌代码搜索只会搜索开源代码,私人的或者企业的代码库(codebase)是不包含在内的。因此这些数据可能还有一定的局限性,不过至少这种方法可以避免一些问题。而WG21中也有很多企业代表,他们也会帮助WG21确定这些关键字是否会导致自己企业代码库中代码不兼容的问题。

C++11的新关键字如下:

image
image

这些新关键字都是相对于C++98/03来说的。当然,引入它们可能会破坏一些C++98/03代码,甚至更为糟糕的是,可能会悄悄地改变了原有C++98/03程序的目的。static_assert就是这样一个例子。为了降低它与已有程序变量冲突的可能性,WG21将这个关键字的名字设计得很长,甚至还包含了下划线,可以说命名丑得不能再丑了,不过在一些情况下,它还是会发生冲突,比如:

static_assert(4<=sizeof(int), "error:small ints");

这行代码的意图是确定编译时(不是运行时)系统的int整型的长度不小于4字节,如果小于,编译器则会报错说系统的整型太小了。在C++11中这是一段有效的代码,在C++98/03中也可能是有效的,因为程序员可能已经定义了一个名为static_assert的函数,以用于判断运行时的int整型大小是否不小于4。显然这与C++11中的static_assert完全不同。

实际上,在C++11中还有两个具有特殊含义的新标识符:override、final。这两个标识符如何被编译器解释与它们所在的位置有关。如果这两个标识符出现在成员函数之后,它们的作用是标注一个成员函数是否可以被重载。不过读者实际上也可以在C++11代码中定义出override、final这样名称的变量。而在这样的情况下,它们只是标识了普通的变量名称而已。

我们主要会在第2章中看到相关的特性的描述。

1.4.2 更倾向于使用库而不是扩展语言来实现特性

相比于语言核心功能的稳定,库则总是能随时为程序员提供快速上手的、方便易用的新功能。库的能量是巨大的,Boost和一些公司私有的库(如Qt、POOMA)的快速成长就说明了这一点。而且库有一个很大的优势,就是其改动不需要编译器实现新特性(只要接口保持一致即可),当然,更重要的是库可以用于支持不同领域的编程。这样一来,通常读者不需要非常精通C++就能使用它们。

不过这些优点并不是被广泛认可的。狂热的语言爱好者总是觉得功能加入语言特性,由编译器实现了才是王道,而库只是第二选择。不过WG21的看法跟他们相反。事实上,如果可能,WG21会尽量将一个语言特性转为库特性来实现。比较典型的如C++11中的线程,它被实现为库特性的一部分:std::thread,而不是一个内置的“线程类型”。同样的,C++11中没有内置的关联数组(associative array)类型,而是将它们实现为如std::unorder_map这样的库。再者,C++11也没有像其他语言一样在语言核心部分加入正则表达式功能,而是实现为 std::regex库。这样一来,C++语言可以尽量在保持较少的核心语言特性的同时,通过标准库扩大其功能。

从传统意义上讲,库可能是通过提供头文件来实现的。当然,有些时候库的提供者也会将一些实现隐藏在二进制代码库存档(archive)文件中。不过并非所有的库都是通过这样的方式提供的。事实上,库也有可能实现于编译器内部。比如C++11中的原子操作等许多内容,就通常不是在头文件或库存档中实现的。编译器会在内部就将原子操作实现为具体的机器指令,而无需在稍后去链接实实在在的库进行存档。而之所以将原子操作的内容放在库部分,也是为了满足将原子操作作为库实现的自由。从这个意义上讲,原子操作并非纯粹的“库”,因此也被我们选择性地纳入了本书的讲解中。

1.4.3 更倾向于通用的而不是特殊的手段来实现特性

如我们说到的,如果将无数互不相关的小特性加入C++中,而且不加选择地批准通过,C++将成为一个令人眼花缭乱的“五金店”,不幸的是,这个五金店的产品虽然各有所长,凑在一起却是一盘散沙,缺乏战斗力。所以WG21更希望从中抽象出更为通用的手段而不是加入单独的特性来“练成”C++11的“十八般武艺”。

显式类型转换操作符是一个很好的例子。在C++98/03中,可以用在构造函数前加上explicit关键字来声明构造函数为显式构造,从而防止程序员在代码中“不小心”将一些特定类型隐式地转换为用户自定义类型。不过构造函数并不是唯一会导致产生隐式类型转换的方法,在C++98/03中类型转换操作符也可以参与隐式转换,而程序员的意图则可能只是希望类型转换操作符在显式转换时发生。这是C++98/03的疏忽,不过在C++11中,我们已经可以做到这点了。

其他的一些新特性,比如继承构造函数、移动语义等,在本书的第3章中我们均会涉及。

1.4.4 专家新手一概支持

如果C++只是适合专家的语言,那它就不可能是一门成功的语言。C++中虽然有许多专家级的特性,但这并不是必须学习的。通常程序员只需要学习一定的知识就可以使用C++。而在C++11中,从易用的角度出发,修缮了很多特性,也铲除了许多带来坏声誉的“毒瘤”,比如一度被群起而攻之的“毒瘤”—双右尖括号。在C++98/03中,由于采用了最长匹配的解析规则(maximal munch parsing rule),编译器会在解析符号时尽可能多地“吸收”符号。这样一来,在模板解析的时候,编译器就会将原本是“模板的模板”识别为右移,并“理直气壮”地抛出一条令人绝望的错误信息:模板参数中不应该存在的右移。如今这个问题已经在C++11中被修正。模板参数内的两个右尖括号会终结模板参数,而不会导致编译器错误。当然从实现上讲,编译器只需要在原来报错的地方加入一些上下文的判断就可以避免这样的错误了。比如:

vector<list<int>> veclist: //C++11中有效,C++98/03中无效

另一个C++11易于上手的例子则是统一初始化语法的引入。C++继承了C语言中所谓的“集合初始化语法”(aggregate initialization syntax,比如a[] = {0, 1,};),而在设计类的时候,却只定义了形式单一的构造函数的初始化语法,比如A a(0, 1)。所以在使用C++98/03的时候,编写模板会遇到障碍,因为模板作者无法知道模板用户会使用哪种类型来初始化模板。对于泛型编程来说,这种不一致则会导致不能总是进行泛型编程。而在C++11中,标准统一了变量初始化方法,所以模板作者可以总是在模板编写中采用集合初始化(初始化列表)。进一步地,集合初始化对于类型收窄还有一定的限制。而类型收窄也是许多让人深夜工作的奇特错误的源头。因此在C++11中使用了初始化列表,就等同于拥有了防止收窄和泛型编程的双重好处。

读者可以在第4章看到C++11是如何增进语言对新手的支持的。

1.4.5 增强类型的安全性

绝对的类型安全对编程语言来说几乎是不可能达到的,不过在编译时期捕捉更多的错误则是非常有益的。在C++98/03中,枚举类会退化为整型,因此常会与其他的枚举类型混淆。这个类型的不安全根源还是在于兼容C语言。在C中枚举用起来非常便利,在C++中却是类型系统的一个大“漏勺”。因此在C++11中,标准引入了新的“强类型枚举”来解决这个问题。

enum class Color { red, blue, green };
int x = Color::red;    //C++98/03中允许,C++11中错误:不存在Color->int的转换
Color y = 7;    //C++98/03中,C++11中错误:不存在int->Color conversion的转换
Color z = red;    //C++98/03中允许,C++11中错误:red不在作用域内
Color c = Color::red;    //C++98/03中错误,C++11中允许

在第5章中,我们会详细讲解诸如此类能够增强类型安全的C++11特性。

1.4.6 与硬件紧密合作

在C++编程中,嵌入式编程是一个非常重要的领域。虽然一些方方圆圆的智能设备外表光鲜亮丽,但是植根于其中的技术基础也常常会是C++。在C++11中,常量表达式以及原子操作都是可以用于支持嵌入式编程的重要特性。这些特性对于提高性能、降低存储空间都大有好处,比如ROM。

C++98/03中也具备const类型,不过它对只读内存(ROM)支持得不够好。这是因为在C++中const类型只在初始化后才意味着它的值应该是常量表达式,从而在运行时不能被改变。不过由于初始化依旧是动态的,这对ROM设备来说并不适用。这就要求在动态初始化前就将常量计算出来。为此标准增加了constexpr,它让函数和变量可以被编译时的常量取代,而从效果上说,函数和变量在固定内存设备中要求的空间变得更少,因而对于手持、桌面等用于各种移动控制的小型嵌入式设备(甚至心率调整器)的ROM而言,C++11也支持得更好。

在C++11,我们甚至拥有了直接操作硬件的方法。这里指的是C++11中引入的原子类型。C++11通过引入内存模型,为开发者和系统建立了一个高效的同步机制。作为开发者,通常需要保证线程程序能够正确同步,在程序中不会产生竞争。而相对地,系统(可能是编译器、内存系统,或是缓存一致性机制)则会保证程序员编写的程序(使用原子类型)不会引入数据竞争。而且为了同步,系统会自行禁止某些优化,又保证其他的一些优化有效。除非编写非常底层的并行程序,否则系统的优化对程序员来讲,基本上是透明的。这可能是C++11中最大、最华丽的进步。而就算程序员不乐意使用原子类型,而要使用线程,那么使用标准的互斥变量mutex来进行临界区的加锁和开锁也就够了。而如果读者还想要疯狂地挖掘并行的速度,或试图完全操控底层,或想找点麻烦,那么无锁(lock-free)的原子类型也可以满足你的各种“野心”。内存模型的机制会保证你不会犯错。只有在使用与系统内存单位不同的位域的时候,内存模型才无法成功地保证同步。比如说下面这个位域的例子,这样的位域常常会引发竞争(跨了一个内存单元),因为这破坏了内存模型的假定,编译器不能保证这是没有竞争的。

struct {int a:9; int b:7;}

不过如果使用下面的字符位域则不会引发竞争,因为字符位域可以被视为是独立内存位置。而在C++98/03中,多线程程序中该写法却通常会引发竞争。这是因为编译器可能将a和b连续存放,那么对b进行赋值(互斥地)的时候就有可能在a没有被上锁的情况下一起写掉了。原因是在单线程情况下常被视为普通的安全的优化,却没有考虑到多线程情况下的复杂性。C++11则在这方面做出了较好的修正。

struct {char a; char b;}

与硬件紧密合作的能力使得C++可以在任何系统编程中继续保持领先的位置,比如说构建设备驱动或操作系统内核,同时在一些像金融、游戏这样需要高性能后台守护进程的应用中,C++的参与也会大大提升其性能。

我们会在第6章看到相关特性的描述。

1.4.7 开发能够改变人们思维方式的特性

C++11中一个小小的lambda特性是如何撬动编程世界的呢?从一方面讲,lambda只是对C++98/03中带有operator()的局部仿函数(函数对象)包装后的“语法甜点”。事实上,在C++11中lambda也被处理为匿名的仿函数。当创建lambda函数的时候,编译器内部会生成这样一个仿函数,并从其父作用域中取得参数传递给lambda函数。不过,真正会改变人们思维方式的是,lambda是一个局部函数,这在C++98/03中我们只能模仿实现该特性。此外,当程序员开始越来越多地使用C++11中先进的并行编程特性时,lambda会成为一个非常重要的语法。程序员将会发现到处都是奇怪的“lambda笑脸”,即;},而且程序员也必须习惯在各种上下文中阅读翻译lambda函数。顺带一提,lambda笑脸常会出现在每一个lambda表达式的终结部分。

另一个人们会改变思维方式的地方则是如何让一个成员函数变得无效。在C++98/03中,我们惯用的方法是将成员函数声明为私有的。如果读者不知道这种方法的用意,很可能在阅读代码的时候产生困惑。不过今天的读者非常幸运,因为在C++11中不再需要这样的手段。在C++11中我们可以通过显式默认和删除的特性,清楚明白地将成员函数设为删除的。这无疑改变了程序员编写和阅读代码的方式,当然,思考问题的方式也就更加直截了当了。

我们会在第7章中看到相关特性的描述。

1.4.8 融入编程现实

现实世界中的编程往往都有特殊的需求。比如在访问因特网的时候我们常常需要输入URL,而URL通常都包含了斜线“/”。要在C++中输入斜线却不是件容易的事,通常我们需要转义字符“/”的配合,否则斜线则可能被误认为是除法符号。所以如果读者在写网络地址或目录路径的时候,代码最终看起来就是一堆倒胃口的反斜线的组合,而且会让内容变得晦涩。而C++11中的原生字符串常量则可免除“转义”的需要,也可以帮助程序员清晰地呈现网络地址或文件系统目录的真实内容。

另一方面,如今GNU的属性(attribute)几乎无所不在,所有的编译器都在尝试支持它,以用于修饰类型、变量和函数等。不过__attribute__((attribute-name))这样的写法,除了不怎么好看外,每一个编译器可能还都有它自己的变体,比如微软的属性就是以__declspec打头的。因此在C++11中,我们看到了通用属性的出现。

不过C++11引入通用属性更大的原因在于,属性可以在不引入额外的关键字的情况下,为编译提供额外的信息。因此,一些可以实现为关键字的特性,也可以用属性来实现(在某些情况下,属性甚至还可以在代码中放入程序供应商的名字,不过这样做存在一些争议)。这在使用关键字还是将特性实现为一个通用属性间就会存在权衡。不过最后标准委员会认为,在现在的情况下,在C++11中的通用属性不能破坏已有的类型系统,也不应该在代码中引起语义的歧义。也就是说,有属性的和没有属性的代码在编译时行为是一致的。所以C++11标准最终选择创建很少的几个通用属性—noreturn和carrier_dependency(其实final、override也一度是热门“人选”)。

属性的真正强大之处在于它们能够让编译器供应商创建他们自己的语言扩展,同时不会干扰语言或等待特性的标准化。它们可以被用于在一些域内加入特定的“方言”,甚至是在不用pragma语法的情况下扩展专有的并行机制(如果读者了解OpenMP,对此会有一些体会)。

我们将在第8章中看到相关的描述。

相关文章
|
2月前
|
Ubuntu API C++
C++标准库、Windows API及Ubuntu API的综合应用
总之,C++标准库、Windows API和Ubuntu API的综合应用是一项挑战性较大的任务,需要开发者具备跨平台编程的深入知识和丰富经验。通过合理的架构设计和有效的工具选择,可以在不同的操作系统平台上高效地开发和部署应用程序。
157 11
|
8月前
|
机器学习/深度学习 文字识别 监控
安全监控系统:技术架构与应用解析
该系统采用模块化设计,集成了行为识别、视频监控、人脸识别、危险区域检测、异常事件检测、日志追溯及消息推送等功能,并可选配OCR识别模块。基于深度学习与开源技术栈(如TensorFlow、OpenCV),系统具备高精度、低延迟特点,支持实时分析儿童行为、监测危险区域、识别异常事件,并将结果推送给教师或家长。同时兼容主流硬件,支持本地化推理与分布式处理,确保可靠性与扩展性,为幼儿园安全管理提供全面解决方案。
432 3
|
9月前
|
存储 负载均衡 算法
基于 C++ 语言的迪杰斯特拉算法在局域网计算机管理中的应用剖析
在局域网计算机管理中,迪杰斯特拉算法用于优化网络路径、分配资源和定位故障节点,确保高效稳定的网络环境。该算法通过计算最短路径,提升数据传输速率与稳定性,实现负载均衡并快速排除故障。C++代码示例展示了其在网络模拟中的应用,为企业信息化建设提供有力支持。
278 15
|
9月前
|
人工智能 API 开发者
HarmonyOS Next~鸿蒙应用框架开发实战:Ability Kit与Accessibility Kit深度解析
本书深入解析HarmonyOS应用框架开发,聚焦Ability Kit与Accessibility Kit两大核心组件。Ability Kit通过FA/PA双引擎架构实现跨设备协同,支持分布式能力开发;Accessibility Kit提供无障碍服务构建方案,优化用户体验。内容涵盖设计理念、实践案例、调试优化及未来演进方向,助力开发者打造高效、包容的分布式应用,体现HarmonyOS生态价值。
606 27
|
8月前
|
编译器 C++ 容器
【c++11】c++11新特性(上)(列表初始化、右值引用和移动语义、类的新默认成员函数、lambda表达式)
C++11为C++带来了革命性变化,引入了列表初始化、右值引用、移动语义、类的新默认成员函数和lambda表达式等特性。列表初始化统一了对象初始化方式,initializer_list简化了容器多元素初始化;右值引用和移动语义优化了资源管理,减少拷贝开销;类新增移动构造和移动赋值函数提升性能;lambda表达式提供匿名函数对象,增强代码简洁性和灵活性。这些特性共同推动了现代C++编程的发展,提升了开发效率与程序性能。
336 12
|
8月前
|
存储 监控 算法
基于 C++ 哈希表算法的局域网如何监控电脑技术解析
当代数字化办公与生活环境中,局域网的广泛应用极大地提升了信息交互的效率与便捷性。然而,出于网络安全管理、资源合理分配以及合规性要求等多方面的考量,对局域网内计算机进行有效监控成为一项至关重要的任务。实现局域网内计算机监控,涉及多种数据结构与算法的运用。本文聚焦于 C++ 编程语言中的哈希表算法,深入探讨其在局域网计算机监控场景中的应用,并通过详尽的代码示例进行阐释。
180 4
|
9月前
|
供应链 项目管理 容器
深入探索 BPMN、CMMN 和 DMN:从定义到应用的全方位解析
在当今快速变化的商业环境中,对象管理组织(OMG)推出了三种强大的建模标准:BPMN(业务流程模型和符号)、CMMN(案例管理模型和符号)和DMN(决策模型和符号)。它们分别适用于结构化流程管理、动态案例处理和规则驱动的决策制定,并能相互协作,覆盖更广泛的业务场景。BPMN通过直观符号绘制固定流程;CMMN灵活管理不确定的案例;DMN以表格形式定义清晰的决策规则。三者结合可优化企业效率与灵活性。 [阅读更多](https://example.com/blog)
深入探索 BPMN、CMMN 和 DMN:从定义到应用的全方位解析
|
9月前
|
存储 弹性计算 安全
阿里云服务器ECS通用型规格族解析:实例规格、性能基准与场景化应用指南
作为ECS产品矩阵中的核心序列,通用型规格族以均衡的计算、内存、网络和存储性能著称,覆盖从基础应用到高性能计算的广泛场景。通用型规格族属于独享型云服务器,实例采用固定CPU调度模式,实例的每个CPU绑定到一个物理CPU超线程,实例间无CPU资源争抢,实例计算性能稳定且有严格的SLA保证,在性能上会更加稳定,高负载情况下也不会出现资源争夺现象。本文将深度解析阿里云ECS通用型规格族的技术架构、实例规格特性、最新价格政策及典型应用场景,为云计算选型提供参考。
|
9月前
|
数据采集 机器学习/深度学习 存储
可穿戴设备如何重塑医疗健康:技术解析与应用实战
可穿戴设备如何重塑医疗健康:技术解析与应用实战
362 4
|
9月前
|
人工智能 自然语言处理 算法
DeepSeek大模型在客服系统中的应用场景解析
在数字化浪潮下,客户服务领域正经历深刻变革,AI技术成为提升服务效能与体验的关键。DeepSeek大模型凭借自然语言处理、语音交互及多模态技术,显著优化客服流程,提升用户满意度。它通过智能问答、多轮对话引导、多模态语音客服和情绪监测等功能,革新服务模式,实现高效应答与精准分析,推动人机协作,为企业和客户创造更大价值。
810 5

推荐镜像

更多
  • DNS