C++14 是 ISO/IEC 14882 标准的一个版本,用于规范 C++ 编程语言。它旨在作为 C++11 的一个小扩展,主要包含错误修复和一些小的改进,并已被 C++17 取代。其批准于 2014 年 8 月 18 日宣布。C++14 于 2014 年 12 月以 ISO/IEC 14882:2014 的名义发布。
由于早期的 C++ 标准修订版明显滞后,因此在正式批准之前,有时会使用"C++1y"这个名称,这与 C++11 标准过去被称为"C++0x"的情况类似,当时预计它会在 2010 年之前发布(尽管实际上它推迟到了 2010 年,最终推迟到了 2011 年)。
新增的语言特征:
1. 函数返回类型推断
C++11 允许 lambda 函数根据 return 语句中表达式的类型推断返回类型。C++14 为所有函数提供了这种能力,并且还将此功能扩展到 lambda 函数,允许非 `return expression;` 形式的函数进行返回类型推断。
为了实现返回类型推断,函数必须声明为 auto作为返回类型,但在 C++11 中,无需在函数末尾添加返回类型说明符:
cpp
auto deduceReturnType();
如果函数实现中使用了多个返回表达式,则它们必须推导出相同的类型。可以预先声明返回类型可推导的函数,但必须先定义才能使用。其定义必须对使用它们的翻译单元可用。递归可以用于此类函数,但递归调用必须发生在函数定义中至少一个 return 语句之后。
cpp
auto correct(int i) {
if (i == 1) {
return i; // 返回类型推断为int
}
return correct(i - 1) + i; // 正确调用
}
auto wrong(int i) {
if (i != 1)
return wrong(i - 1) + i; // 现在下结论还为时过早。此前没有相关声明。
}
return i; // 返回类型推断为int
}
2. 基于声明的类型推断
在 C++11 中,新增了两种类型推导方法。auto 可以根据给定的表达式创建相应类型的变量。decltype则用于计算给定表达式的类型。然而,decltype 和 auto 的类型推导方式不同。具体来说,auto 总是推导非引用类型,如同使用了 std::decay 一样,而 auto && 总是推导引用类型。但是,decltype 可以根据表达式的值类别和表达式的性质来推导引用类型或非引用类型:
cpp
int i;
int&& f();
auto x3a = i; // decltype(x3a) is int
decltype(i) x3d = i; // decltype(x3d) is int
auto x4a = (i); // decltype(x4a) is int
decltype((i)) x4d = (i); // decltype(x4d) is int&
auto x5a = f(); // decltype(x5a) is int
decltype(f()) x5d = f(); // decltype(x5d) is int&&
C++14 新增了 decltype(auto) 语法。这使得 auto 声明能够对给定的表达式应用 decltype规则。
decltype(auto) 语法也可以用于返回类型推导,只需在函数返回类型推导中使用 decltype(auto) 语法代替 auto 即可。
3. 放宽 constexpr限制
C++11 引入了 constexpr声明函数的概念;这种函数可以在编译时执行。它们的返回值可以被需要常量表达式的操作使用,例如整数模板参数。然而,C++11 constexpr 函数只能包含一个返回表达式(以及 static_assert 和少量其他声明)。
C++14 放宽了这些限制。constexpr声明的函数现在可以包含以下内容:
除以下类型的所有类型:
(1) static或 thread_local 变量
(2) 没有初始化器的变量声明
条件分支语句 if和 switch
任意循环语句,包括范围for
如果对象的生命周期始于常量表达式函数内部,则该表达式会改变对象的值。这包括对任何非 const constexpr 声明的非静态成员函数的调用。
在 C++14 宽松版 constexpr 声明的函数中,禁止使用 goto语句。
此外,C++11 曾规定,所有声明为 constexpr 的非静态成员函数都会隐式声明为 const。但该规定已被移除;非静态成员函数可以是非 const的。然而,根据上述限制,非 const 的 constexpr成员函数只能在类成员的生命周期开始于常量表达式求值期间的情况下才能修改该类成员。
4. 变量模板(或模板变量)
在之前的 C++ 版本中,只有函数、类或类型别名可以进行模板化。C++14 允许创建可模板化的变量 。提案中给出的一个例子是变量 pi,可以读取该变量的值来获取不同类型下的 pi 值(例如,当读取为整数类型时,返回 3;当分别读取为 float 、double 或long double 类型时,返回最接近该值的 float 、double 或 long double精度值;等等)。
模板的通常规则适用于此类声明和定义,包括特化。
cpp
template <typename T>
constexpr T PI = T(3.141592653589793238462643383);
// Usual specialization rules apply:
template <>
constexpr const char* PI<const char*> = "pi";
5. 聚合成员初始化(Aggregate)
C++11 添加了默认成员初始化器,即在类作用域内,如果构造函数没有初始化成员,则对成员应用默认成员初始化器的表达式。聚合类型的定义也进行了修改,明确排除了任何带有成员初始化器的类;因此,它们不允许使用聚合初始化。
C++14 放宽了这一限制,允许对这类类型进行聚合初始化。如果花括号括起来的初始化列表没有为该参数提供值,则由成员初始化器来处理。
5. 二进制文字量
C++14 中的数值字面量可以用二进制形式指定。语法使用前缀 0b 或 0B。其他语言也使用这种语法,例如 Java、C#、Swift、Go、Scala、Ruby、Python、OCaml,并且自 2007 年以来,一些 C 编译器也将其作为非官方扩展。
6. 数字分隔符
在 C++14 中,单引号字符可以任意用作数值字面量(包括整数字面量和浮点字面量)中的数字分隔符。这可以使人类读者更容易通过快速计数来解析大数字。
cpp
auto integer_literal = 1'000'000;
auto floating_point_literal = 0.000'015'3;
auto binary_literal = 0b0100'1100'0110;
auto a_dozen_crores = 12'00'00'000;
7. 泛型 lambda 表达式
在 C++11 中,lambda 函数的参数必须使用具体类型声明。C++14 放宽了这一要求,允许使用 auto 类型说明符声明 lambda 函数的参数。
cpp
auto lambda = [](auto x, auto y) -> auto {
return x + y;
};
关于自动类型推导,泛型 lambda 表达式遵循模板参数推导的规则(两者类似,但并非完全相同[需要澄清])。上面的代码等价于以下代码:
cpp
struct {
template <typename T, typename U>
auto operator()(T x, U y) const {
return x + y;
}
} lambda {};
泛型 lambda 本质上是模板化的函子(functor) lambda。
8. Lambda 捕获表达
C++11 lambda 函数可以通过值复制或引用捕获在其外部作用域中声明的变量。这意味着 lambda 函数的值成员不能是仅可移动类型。 C++14 允许使用任意表达式初始化捕获的成员。这使得既可以通过值移动进行捕获,也可以声明 lambda 函数的任意成员,而无需在外部作用域中拥有相应名称的变量。
这是通过使用初始化表达式来实现的:
cpp
auto lambda = [value = 1] -> int {
return value;
};
lambda 函数 lambda 返回 1,即值初始化时使用的值。声明的捕获会像使用 auto一样,从初始化表达式中推断类型。
这可以通过使用标准的 std::move 函数来实现按移动捕获:
cpp
using std::unique_ptr;
unique_ptr<int> ptr(new int(10));
auto lambda = [value = std::move(ptr)] -> int {
return *value;
};
9. depredicated属性
\[**deprecated** \]\] 属性允许将实体标记为已弃用,这虽然仍然允许使用,但会提醒用户不建议使用,并且可能会在编译时打印警告信息。\[\[**deprecated** ("Reason")\]\] 可以接受一个可选的字符串字面量作为参数,用于解释弃用原因并建议替代方案。
```cpp
[[deprecated]]
int f();
[[deprecated("g() is thread-unsafe. Use h() instead")]]
void g(int& x);
void h(int& x);
void test() {
int a = f(); // warning: 'f' is deprecated
g(a); // warning: 'g' is deprecated: g() is thread-unsafe. Use h() instead
}
```
## 新的标准库特征
### 1. 共享互斥锁及锁类型
C++14 增加了共享定时互斥锁和相应的共享锁类型。
### 2. 关联容器中的异构(Heterogeneous)查找
C++ 标准库定义了四个关联容器类。这些类允许用户根据指定类型的值查找目标值。映射容器允许用户指定键和值,查找通过键进行,并返回一个值。然而,查找始终基于特定的键类型,无论是映射中的键,还是集合中的值本身。
C++14 允许通过任意类型进行查找,只要比较运算符能够将该类型与实际的键类型进行比较即可。\[这使得从 **std::string** 到某个值的映射可以与 **const char\*** 或任何其他支持 **operator\<** 重载的类型进行比较。它还有助于通过单个成员的值对**std::set**中的复合对象进行索引,而无需强制 **find** 的用户创建一个虚拟对象(例如,创建一个完整的 **struct Person**来按姓名查找人员)。
为了保持向后兼容性,只有当传递给关联容器的比较器允许时,才允许异构查找。标准库类 **std::less\<\>**和 **std::greater\<\>** 已进行扩展,以允许异构查找。
### **3. 标准用户定义文字量(literals)**
C++11 定义了用户自定义字面量后缀的语法,但标准库并未使用这些后缀。C++14 添加了以下标准字面量:
 "s", 用于创建各种 **std::basic_string**类型。
 "h", "min", "s", "ms", "us", "ns", 用于创建相应的 **std::chrono::duration** 时间间隔。
 "if", "i", "il", 用于创建相应的**std::complex\