📔 第 18 章 探讨C++新标准 学习笔记
1. 复习C++11功能¶
1.1 新类型¶
C++11新增类型:
- 整型
long longunsigned long long
- 字符表示
char16_tchar32_t
1.2 统一的初始化¶
C++11 扩大了用大括号起的列表(初始化列表)的适用范围。使其可用于所有内置类型和用户定义类型(即类对象)。列表初始化的三种方式:
- 使用初始化列表时,可添加
等号(=),也可不添加。
- 可以用于
new表达式中
- 创建对象时,可使用
大括号(而不是圆括号)括起来的列表来调用构造函数。
1.2.1 缩窄¶
-
初始化列表语法可防止缩窄,即禁止将数值赋给无法存储它的数值变量,即将值存储到比它“窄”的变量中。
-
编译器会禁止变窄类型的转换,但允许在范围内变宽的转换。
1.2.2 std::initializer_list¶
-
C++11中提供
initializer_list模板类,可用作构造函数的参数。 -
如果类有接受
initializer_list作为参数的构造函数,则初始化列表语法就只能用于该构造函数。 - 列表中的元素必须是
同一种类型或者可转换为同一种类型。
1.3 声明¶
1.3.1 auto¶
C++11使用auto关键字实现自动类型推断。-
要求进行显式初始化,让编译器能够将变量的类型设置为初始值的类型。
-
auto关键字也可简化模板声明。
1.3.2 decltype¶
- decltype关键字:将变量的类型声明为表达式指定的类型。
1.3.3 返回类型后置¶
- C++11新增函数声明语法:在函数名和参数列表后面指定返回类型。
- 使用 decltype来指定模板函数的返回类型:
1.3.4 模板别名:using¶
解决标识符冗长或复杂的问题。
-
早期使用
typedef- 不能用于模板部分具体化
-
C++11提供
using=- 可用于模板部分具体化
1.3.5 nullptr¶
C++11 新增关键字 nullptr来表示空指针。它是指针类型,不能转换为整型类型。
为向后兼容,C++11仍允许使用 0 来表示空指针,因此表达式 nullptr==0 为 true。
1.4 智能指针¶
因为在程序中使用 new 分配内存,未将其及时 delete释放。引入智能指针auto_ptr。
C++11抛弃auto_ptr,并新增了三种智能指针:unique_ptr、shared_ptr 和 weak_ptr。
1.5 异常规范的修改¶
指出函数不会引发异常有一定的价值,所以C++11添加了关键字noexpect:
1.6 作用域内枚举¶
在C++11中新增一种枚举。使用 class 或者 struc t来定义。
新枚举要求进行显式限定,以避免名称冲突。所以在引用枚举时,则使用
1.7 对类的修改¶
-
显式转换运算符- 早期的自动类型转换会导致意外问题,所以在C++11中引入关键字
explicit,以禁止单参数构造函数导致的自动转换。
- 早期的自动类型转换会导致意外问题,所以在C++11中引入关键字
-
类内成员初始化- 可使用等号或者大括号版本的初始化,但不能使用圆括号版本的初始化。
- 类内成员初始化,可避免在构造函数中编写重复的代码。
1.8 模板和STL方面的修改¶
-
基于范围的for循环
- 如果要在循环中修改数组或容器中的每个元素,可使用引用类型。
-
新的STL容器
- C++11新增STL容器:
forward_list,unordered_map,unordered_multimap,unordered_set,unordered_multiset。 - C++11新增模板array,实例化时,可指定元素类型和固定的元素数。
- C++11新增STL容器:
-
新的STL方法
- C++11新增STL方法
cbegin()cend()crbegin()crend()
- 其中
crbegin()、crend()是rbegin()、rend()的const版本。 - 除传统的
复制构造函数和常规赋值运算符外,STL容器还有移动构造函数和移动赋值运算符。
- C++11新增STL方法
-
valarray升级
- C++11中添加了两个函数 begin() 和 end()。都接受 valarray 作为参数,并返回迭代器。
-
抛弃export
- C++98新增
- C++终止用法,但为了兼容还保留关键字 export。
-
尖括号
- C++在声明嵌套模板时不再需要使用空格将尖括号分开。
1.9 右值引用¶
-
左值引用
- 左值:一个表示数据的表达式,程序可获取其地址。
- 左值出现在赋值语句的左边,但修饰符的出现const使得声明的标识符,即不能给它赋值,但可获取其地址。
-
右值引用
- C++11新增
右值引用,使用&&表示。右值引用可以关联到右值,即可出现在赋值表达式右边,但不能对其应用地址运算符的值。 右值包括:字面常量、表达式和返回值的函数(函数返回的不能是引用)。- 引入右值引用的主要目的之一:实现
移动语义。
- C++11新增
2. 移动语义和右值引用¶
2.1 需要移动语义的原因¶
-
常规复制构造函数- 使用
const左值引用作为参数,使得引用关联到左值实参。 - 可执行深复制
- 使用
-
移动构造函数- 使用
右值引用作为参数,将引用关联到右值实参。 - 只调整记录
- 在将所有权转移给新对象的过程中,移动构造函数可能修改其实参,意味着
右值引用参数不应是const。
- 使用
2.2 移动构造函数¶
2.3 赋值¶
- 适用于构造函数的移动语义考虑也适用于赋值运算符。
3. 新的类功能¶
3.1 特殊的成员函数¶
-
原有的4个特殊成员函数
- 默认构造函数
- 复制构造函数
- 复制赋值运算符
- 析构函数
-
C++新增的2个
- 移动构造函数
- 移动赋值运算符
这些成员函数是编译器在各种情况下自动提供的。
- 如果提供了
析构函数、复制构造函数或者复制赋值运算符,编译器将不会自动提供移动构造函数和移动赋值运算符。 - 如果提供了
移动构造函数或移动赋值运算符,编译器将不会自动提供复制构造函数和复制赋值运算符。
3.2 默认的方法和禁用的方法¶
- C++11中如果提供了
移动构造函数,所以编译器不会自动创建默认的构造函数、复制构造函数和复制赋值构造函数。可使用关键字 default显式声明方法的默认版本。
delete 可用于禁止编译器使用特定方法。
- 也可禁止特定的转换
- 关键字
default只能用于6个特殊成员函数,但delete可用于任何成员函数。
3.3 委托构造函数¶
- C++11 允许在一个构造函数的定义中使用另一个构造函数,这种方式称为
委托。
3.4 继承构造函数¶
-
C++98 使用
名称空间中函数可用的方式。 -
C++11 中使用
派生类继承基类的所有构造函数(默认构造函数、复制构造函数和移动构造函数除外),但不会使用与派生类构造函数的特征标匹配的构造函数。
3.5 管理虚方法:override和 final¶
-
在C++11中,可使用
虚说明符override指出要覆盖一个函数,将其放在参数列表后面。如果声明与基类方法不匹配,编译器将视为错误。 -
final说明符:禁止派生类覆盖特定的虚方法,所以在参数列表后面加上final。 -
override和final并非关键字,而是具有特殊含义的标识符。
4. Lambda函数¶
Lambda 函数,也叫做Lambda表达式。
匿名函数无需给出函数命名。
4.1 比较函数指针、函数符和Lambda函数¶
在C++11中,对于接受函数指针或者函数符的函数,可使用匿名函数定义作为其参数。
匿名函数与函数的区别:
- 使用
[]替代函数名 没有声明返回类型(返回类型相当于使用decltype根据返回值推断得到)- 如果
lambda不包含返回语句,推断出的返回类型将为void。
当且仅当, lambda表达式完全由一条返回语句组成时,自动类型推断才管用。否则,需要使用新增的返回类型后置语法:
4.2 使用Lambda原因¶
四个角度来分析
- 距离
- 函数定义离使用地点近。
- 简洁
- 函数符代码比函数和lambda代码更繁琐。
- 效率
- 三种方式的相对效率取决于编译器内联。
- 功能
- lambda可访问
作用域内的任何动态变量 - 要捕获使用的变量,可将其名称放在中括号内。
- 如果
只指定变量名,如[z],则将值访问变量 - 如果在
名称前加上 &,如[&count],则按引用访问变量
- 如果
[&]按引用访问所有动态变量[=]按值访问所有动态变量。
- lambda可访问
函数指针方法阻止内联,因为编译器传统上不会内联其他地址被获取的函数,因为函数地址的概念意味着非内联函数。而函数符和lambda通常不会阻止内联。
可以给lambda指定一个名称,然后使用名称来替代。
C++中引入lambda的主要目的:能将类似于函数的表达式用做接受函数指针或函数符的函数的参数。
5. 包装器¶
C++提供多个包装器(wrapper,也叫做适配器(adapter))。
C++11中提供了其他的包装器:
bind- 可替代
bind1st和bind2nd
- 可替代
mem_fn- 可将成员函数作为常规函数进行传递
reference_wrapper- 能创建行为像引用但可被复制的对象
function- 以统一的方式处理多种类似于函数的形式
5.1 包装器function及模板的低效性¶
模板function 是在头文件functional 中声明,从调用特征标的角度定义了一个对象,可用于包装调用特征相同的函数指针、函数对象或者lambda表达式。
6. 可变参数模板¶
要创建可变参数模板,需要理解以下要点:
- 模板参数包(parameter pack)
- 函数参数包
- 展开参数包
- 递归
6.1 模板和函数参数包¶
C++11中提供省略号表示的元运算符(meta-operator)。
模板参数包:可以声明表示模板参数包的标识符,模板参数包是一个类型列表。函数参数包:可以声明表示函数参数包的标识符,函数参数包是一个值列表。
6.2 展开参数包中使用递归¶
将函数参数包展开,对列表中的第一项进行处理,再将余下的内容传递给递归调用,以此类推,直到列表为空。
7. C++11新增的功能¶
7.1 并行编程¶
为解决并行性的问题,C++定义了一个支持线程化执行的内存模型,添加了关键字 thread_local ,提供了相关库支持。
关键字 thread_local 将变量声明为静态存储,其持续性和特定线程相关,即定义变量的线程过期时,变量也过期。
库支持由原子操作(automic、operation)库和线程支持库组成。原子操作库提供的头文件:
threadmutexconditionvariablefuture
7.2 新增的库¶
在C++11中新增专用的头文件库
- random:随机数扩展工具
- chrono:处理时间间隔的途径
- tuple:支持模板tuple。
- ratio:编译阶段的有理数算术库
- regex:正则表达式库。用于指定与文本字符串的内容匹配。
7.3 低级编程¶
低级编程中的“低级”指的是抽象程度,而不是编程质量。
C++11给低级编程人员提供的两个便利:
- 放松了
POD(Plain Old Data)的要求。 - 允许
共用体的成员有构造函数和析构函数。