C++11 新增的其他功能
C++11 增加了很多功能,本书无法全面介绍;另外,本书编写期间,其中很多功能还未得到广泛实现。然而,有些功能有必要简要地介绍一下。
并行编程
当前,为提高计算机性能,增加处理器数量比提高处理器速度更容易。因此,装备了双核、四核处理器甚至多个多核处理器的计算机很常见,这让计算机能够同时执行多个线程,其中一个处理器可能处理视频下载,而另一个处理器处理电子表格。
有些操作能受益于多线程,但有些不能。考虑单向链表的搜索:程序必须从链表头开始,沿链接依次向下搜索,直到到达链表末尾;在这种情况下,多线程的帮助不大。再来看未经排序的数组。考虑到数组的随机存取特征,可让一个线程从数组开头开始搜索,并让另一个线程从数组中间开始搜索,这将让搜索时间减半。
多线程确实带来了很多问题。如果一个线程挂起或两个线程试图同时访问同一项数据,结果将如何呢?为解决并行性问题,C++ 定义了一个支持线程化执行的内存模型,添加了关键字 thread_local,提供了相关的库支持。关键字 thread_local 将变量声明为静态存储,其持续性与特定线程相关;即定义这种变量的线程过期时,变量也将过期。
库支持由原子操作(atomic operation)库和线程支持库组成,其中原子操作库提供了头文件 atomic,而线程支持库提供了头文件 thread、mutex、condition_variable 和 future。
新增的库
C++11 添加了多个专用库。头文件 random 支持的可扩展随机数库提供了大量比 rand() 复杂的随机数工具。例如,您可以选择随机数生成器和分布状态,分布状态包括均匀分布(类似于 rand()、二项分布和正态分布等。
头文件 chrono 提供了处理时间间隔的途径。
头文件 tuple 支持模板 tuple。tuple 对象是广义的 pair 对象。pair 对象可存储两个类型不同的值,而 tuple 对象可存储任意多个类型不同的值。
头文件 ratio 支持的编译截短有理数算术库让您能够准确地表示任何有理数,其分子和分母可用最宽的整型表示。它还支持对这些有理数进行算术运算。
在新增的库中,最有趣的一个是头文件 regex 支持的正则表达式库。正则表达式指定了一种模式,可用于与文本字符串的内容匹配。例如,方括号表达式与方括号中的任何单个字符匹配,因此[cCkK] 与 c、C、k 和 K 都匹配,而[cCkK] at 与单词 cat、Cat、kat 和 Kat 都匹配。其他模式包括与一位数字匹配的 \d、与一个单词匹配的 \w、与制表符匹配的 \t 等。在 C++ 中,斜杠具有特殊含义,因此对于模式 \d\t\w\d(即依次为一位数字、制表符、单词和一位数字),必须写成字符字面量“\d\t\w\d”,即使用\表示\。这是引入原始字符串的原因之一(参见第4章),它让您能够将该模式写成 R"\d\t\w\d"。
ed、grep 和 awk 等 UNIX 工具都使用正则表达式,而解释型语言 Perl 扩展了正则表达式的功能。C++ 正则表达式库让您能够选择多种形式的正则表达式。
低级编程
低级编程中的“低级”指的是抽象程度,而不是编程质量。低级意味着接近于计算机硬件和机器语言使用的比特和字节。对嵌入式编程和改善操作的效率而言,低级编程很重要。C++11 给低级编程人员提供了一些帮助。
变化之一是放松了 POD(Plain Old Data)的要求。在 C++98 中,POD 是标量类型(单值类型,如 int 或 double)或没有构造函数、基类、私有数据、虚函数等的老式结构。以前的理念是,POD 是可安全地逐字节复制的东西。这种理念没变,但 C++11 认识到,在满足 C++98 的某些约束的情况下,仍可以是合法的 POD。这有助于低级编程,因为有些低级操作(如使用C语言函数进行逐字节复制或二进制I/O)要求处理对象为 POD。
另一项修改是,允许共用体的成员有构造函数和析构函数,这让共用体更灵活;但保留了其他一些限制,如成员不能有虚函数。在需要最大程度地减少占用的内存时,通常使用共用体;上述新规则在这些情况下给程序员有更大的灵活性和功能。
C++11 解决了内存对齐问题。计算机系统可能对数据在内存中的存储方式有一定的限制。例如,一个系统可能要求 double 值的内存地址为偶数,而另一个系统可能要求其起始位置为 8 的整数倍。要获悉有关类型或对象的对齐要求,可使用运算符 alignof() (参见附录E)。要控制对齐方式,可使用说明符 alignas。
constexpr 机制让编译器能够在编译阶段计算结果为常量的表达式,让 const 变量可存储在只读内存中,这对嵌入式编程来说很有用(在运行阶段初始化的变量存储在随机访问内存中)。
杂项
C99 引入了依赖于实现的扩展模型,C++11继承了这种传统。在使用128位整数的系统中,可使用这样的类型。在 C 语言中,扩展类型由头文件 stdint.h支持,而在 C++ 中,为头文件 cstdint。
C++11 提供了一种创建用户自定义字面量的机制:字面量运算符(literal operator)。使用这种机制可定义二进制字面量,如 1001001b。相应的字面量运算符将把它转换为整数值。
C++ 提供了调试工具 assert。这是一个宏,它在运行阶段对断言进行检查,如果为 true,则显示一条消息,否则调用 abort()。断言通常是程序员认为在程序的某个阶段应为 true 的东西。C++11 新增了关键字 static_assert,可用于在编译阶段对断言进行测试。这样做的目的在于,对于在编译阶段(而不是运行阶段)实例化的模板,调试起来将更简单。
C++11 加强了对元编程(metaprogramming)的支持。元编程指的是编写这样的程序,它创建或修改其他程序,甚至修改自身。在 C++ 中,可使用模板在编译阶段完成这种工作。
语言变化
计算机语言是如何成长和发展的呢?C++的使用范围足够广后,显然需要国际标准,并将其控制权交给标准委员会:最初是 ANSI 委员会,随后是 ISO/ANSI 联合委员会,当前是 ISO/IEC JTCI/SC22/WG21(C++标准委员会)。ISO 是国际标准组织,IEC 是国际电子技术委员会,JEC1 是前两家组织组建的联合技术为鸢尾 1, SC22 是 JTC1 下属的编程语言委员会,而 WG21 是 SC22 下属的 C++ 工作小组。
委员会考虑缺陷报告和有关语言修改和扩展的提议,并试图达成一致。这个过程既繁琐又漫长,《The Dsign and Evolution of C++》(Stroustrup,Addison-Wesley,1994)介绍了这方面的一些情况。寻求一致的委员会沉闷而争议不断,可能不是鼓励创新的好方式,这也不是标准委员会应扮演的角色。
但就 C++ 而言,还有另外一种变更的途径,那就是充满创意的 C++ 编程社区的直接行动。程序员无法不受羁绊地改进语言,但可创建有用的库。设计良好的库可改善语言的用途和功能,提高可靠性,让编程更容易、更有乐趣。库是在现有语言功能的基础上创建的,不需要额外的编译器支持。如果库是通过模板实现的,则可以头文件(文本文件)的方式分发。
一项这样的变革是 STL,它主要是 Alexander Stepanov 创建的,Hewlett-Packard 免费提供它。STL 在编程社区获得了巨大成功,成了第一个 ANSI/ISO 标准的候选内容。事实上,其设计影响新标准的其他方面。
Boost 项目
最近,Boost 库成了 C++ 编程的重要部分,给 C++11 带来了深远影响。Boost 项目发起于 1998 年,当时的 C++ 库工作小组主席 Beman Dawes 召集其他几位小组成员指定了一项计划,准备在标准委员会的框架外创建新库。该计划的基本理念是,创建一个充当开放论坛的网站,让人发布免费的 C++ 库。这个项目提供有关许可和编程实践的指南,并要求对提议的库进行同行审阅。其最终的成果是,一系列得到高度赞扬和广泛使用的库。这个项目提供了一个环境,让编程社区能够检验和评估编程理念以及提供反馈。
TR1
TR1(Technical Report 1)是 C++ 标准委员会的部分成员发起的一个项目,它是一个库扩展选集,这些扩展与 C++98 标准兼容,但不是必不可少的。这些扩展是下一个 C++ 标准的候选内容。TR1 库让 C++ 社区能够检验其组成部分的价值。当标准委员会将 TR1 的大部分内容融入 C++11 时,面对的是众所皆知且经过实践检验的库。
在 TR1 中,Boost 库占了很大一部分。这包括模板类 tuple 和 array、模板 bind 和 function、智能指针(对名称和实现做了一定的修改)、static_assert、regex 库 和 random 库。另外,Boost 社区和 TR1 用户的经验也导致了实际的语言变更,如异常规范的摒弃和可变参数模板的添加,其中可变参数模板让 tuple 模板类和 function 模板的实现更好了。
使用 Boost
虽然在 C++11 中,可访问 Boost 开发的众多库,但还有很多其他的 Boost 库。例如,Conversion 库中的 lexical_cast 让您能够在数值和字符串类型之间进行简单地转换,其语法类似于 dynamic_cast:将模板参数指定为目标类型。下面的程序是一个简单示例:
// lexcast.cpp -- simple cast from float to string
#include<iostream>
#include<string>
#include"boost/lexical_cast.hpp"
int main() {
using namespace std;
cout << "Enter your weight: ";
float weight;
cin >> weight;
string gain = "A 10% increase raises ";
string wt = boost::lexical_cast<string>(weight);
gain = gain + wt + " to "; // string operator+()
weight = 1.1 * weight;
gain = gain + boost::lexical_cast<string>(weight) + ".";
cout << gain << endl;
return 0;
}
下面是两次运行该程序的情况:
Enter your weight: 150
A 10% increase raises 150 to 165.
Enter your weight: 156
A 10% increase raises 156 to 171.600006.
第二次运行的结果凸显了 lexical_cast 的局限性:它未能很好地控制浮点数的格式。为控制浮点数的格式,需要使用更精致的内核格式化工具,这在第17章讨论过。
还可以使用 lexical_cast 将字符串转换为数值。
显然,Boost 提供的功能比这里介绍的要多得多。例如,Any 库让您能够在 STL 容器中存储一系列不同类型的值和对象,方法是将 Any 模板用作各种值的包装器。Math 库在标准 math 库的基础上增加了数学函数。Filesystem 库让您编写的代码可在使用不同文件系统的平台之间移植。有关这个库以及如何将其加入到各种平台的更详细消息,请参阅 Boost 网站(www.boost.org)。另外,有些 C++ 编译器(如 Cygwin 编译器)还自带了 Boost 库。
接下来的任务
如果仔细阅读了本书,则应很好地掌握了 C++ 的规则。然而,这仅仅是学习这种语言的开始,接下来需要学习如何高效地使用该语言,这样的路更长。最好的情况是,工作或学习环境让您能够接触优秀地 C++ 代码和程序员。另外,了解 C++ 后,便可以阅读一些介绍高级主题和面向对象编程的书籍,附录 H 列出了一些这样的资源。
OOP 有助于开发大型项目,并提高其可靠性。OOP 方法的基本活动之一是发明能够表示正在模拟的情形(被称为问题域(problem domain))的类。由于实际问题通常很复杂,因此找到适当的类富有挑战性。创建复杂的系统时,从空白开始通常不可行,最好采用逐步迭代的方式。为此,该领域的实践者开发了多种技术和策略。具体地说,重要的是在分析和设计阶段完成尽可能多的迭代工作,而不要不断地修改实际代码。
常用的技术有两种:用例分析(use-case analysis)和 CRC 卡(CRC card)。在用例分析中,开发小组列出了常见的使用方式或最终系统将用于的场景;找出元素、操作和职责,以确定可能要使用的类和类特性。CRC(Class/Responsibilities/Collaborators 的简称)卡片是一种分析场景的简单方法。开发小组为每个类创建索引卡片,卡片上列出了类名、类责任(如表示的数据和执行的操作)以及类的协作者(如必须与之交互的其他类)。然后,小组使用 CRC 卡片提供的接口模拟场景。这可能提出新的类、转换责任等。
在更大的规模上,是用于整个项目的系统方法。在这方面,最新的工具是统一建模语言(Unified Modeling Language,UML),它不是一种编程语言,而是一种用于表示编程项目的分析和设计语言,是由 Grady Booch、Jim Rumbaugh 和 Ivar Jacobson 开发的,他们分别是更早的 3 种建模语言(Booch Method、OMT(对象建模技术,Object Modeling Technique)和 OOSE(面向对象的软件工程,Object-Oriented Software Engineering))的主要开发人员。UML 是从这3种语言演化而来的,于2005年被 ISO/IEC 批准为标准。
除加深对 C++ 的总体理解外,还可能需要学习特定的类库。例如,Microsoft 和 Embracadero 提供了大量简化 Windows 编程的类库,而 Apple Xcode 提供了简化 Apple 平台(如 iPhone)编程的类库。
总结
C++新标准新增了大量功能。有些旨在让 C++ 更容易学习和使用,这包括用大括号括起的统一的列表初始化、使用 auto 自动推断类型、类内成员初始化以及基于范围的 for 循环;而有些旨在增强类设计以及使其更容易理解,这包括默认的和禁用的方法、委托构造函数、继承构造函数以及让虚函数设计更清晰的说明符 override 和 final。
有几项改进旨在提供程序和编程效率。lambda 表达式比函数指针和函数符更好,模板 function 可用于减少模板实例数量,右值引用让您能够使用移动语义以及实现移动构造函数和移动赋值运算符。
其他改进提供了更佳的工作方式。作用域内枚举让您能够更好地控制枚举的作用域和底层类型;模板 unique_ptr 和 shared_ptr 让您能够更好地处理使用 new 分配的内存。
新增的 decltype、返回类型后置、模板别名和可变参数模板让模板设计得到了改进。
修改后的共用体 和 POD 规则、alignof() 运算符、alignas 说明符以及 constexpr 机制支持低级编程。
新增了多个库(包括新的 STL 类、tuple 模板 和 regex 库)为众多常见的编程问题提供了解决方案。
为支持并行编程,新标准还添加了关键字 thread_local 和 atomic 库。
总之,无论对新手还是专家来说,新标准都改善了 C++ 的可用性和可靠性。