“Let’s begin!”
6.1 常量表达式
6.1.1 运行时常量性与编译时常量性
const修饰的函数返回值,只保证了在运行时期内其值是不可以被更改的。C++11中对编译时期常量的回答是constexpr,即常量表达式。
constexpr int GetConst() { return 1; }
在C++11中,常量表达式实际上可以作用的实体不仅限于函数,还可以作用于数据声明以及类的构造函数等
6.1.2 常量表达式函数
通常我们可以在函数返回类型前加入关键字constexpr来使其成为常量表达式函数。
· 函数体只有单一的return返回语句
· 函数必须返回值
· 在使用前必须已有定义
· return返回语句表达式中不能使用非常量表达式的函数、全局数据,且必须是一个常量表达式
· constexpr不会使得函数重写
· 常量表达式中,不能使用非常量表达式的函数
6.1.3 常量表达式值
通常情况下,常量表达式址必须被一个常量表达式赋值,而跟常量表达式函数一样,常量表达式值在使用前必须被初始化
constexpr int j = 1;
// 注意:C++11标准中,constexpr关键字是不能用于修饰自定义类型的。除非自定义类型的定义的构造函数用constexpr关键字修饰
struct MyType {
constexpr MyType(int x) : i(x) {}
int i;
};
constexpr MyType mt = {0};
常量表达式的构造函数也有使用上的约束,主要的有以下两点:
· 函数体必须为空
· 初始化列表只能由常量表达式来赋值
· 在C++11中,不允许敞亮表达式作用于virtual的成员函数。这个原因是显而易见的,virtual表示的是运行时行为,与constexpr表达的含义是相违背的
6.2 变长模板
6.2.1 变长函数和变长的模板参数
在一些情况下,类需要不定义长度的模板参数。最为经典的就是C++11标准库中的tuple类模板。比起pair,tuple是可以接受任意多个不同类型的元素的集合
std::tuple<double, char, std::string> collections;
std::make_tuple(9.8, 'h', "hello");
// 这里的tuple就用到了变长模板
6.2.2 变长模板:模板参数包和函数参数包
templtae<typename... Elements> class tuple;
tuple<int, char, double> t;
与普通的模板参数类似,模板参数也可以是非类型的
template<int... A> class NonTypeVariadicTemplate {};
NonTypeVariadicTemplate<1, 0, 2> ntvt;
一个模板参数包在模板推导时会被认为是模板的单个参数。为了使用模板参数包,我们总是需要解包。通过一个名为包扩展的表达式来实现的。
参数包会在包扩展的位置展开为多个参数。
6.2.3 变长模板:进阶
标准定义了以下7种参数包可以展开的位置:
· 表达式
· 初始化列表
· 基类描述列表
· 类成员初始化列表
· 模板参数列表
· 通用参数列表
· lambda函数的捕捉列表
6.3 原子类型与原子操作
6.3.1 并行编程、多线程与C++11
C/C++对线程的支持,一个最为重要的部分,就是原子操作中引入了原子类型的概念
6.3.2 原子操作与C++11原子类型
所谓原子操作,就是多线程程序中“最小的且不可并行化”的操作。通常情况下,原子操作都是通过“互斥”的访问来保证的。
C++11中简化了多线程编程的开发成本
#include <atomic>
#include <thread>
#include <iostream>
using namespace std;
atomic_llong total {0};
...
C++11中,程序员不需要为原子数据类型显式地声明互斥所或调用加锁、解锁的API,县城就能够对变量total互斥地进行访问
6.4线程局部存储
线程局部存储(TLS)是一个已有的概念。简单地说,所谓线程局部变量,就是拥有线程生命期及线程可见性的变量。C++11中对TLS做出了如下定义:
int thread_local errCode;