C++14新特性个人总结
C++14 是继 C++11 之后的第一个小更新版本(被称为 "C++11 的小兄弟"),主要目标是完善 C++11 的特性、修复缺陷、提供更好的编译期编程支持以及增加一些实用的小改进,而不是引入革命性的新概念。它让代码更简洁、更泛型、更强大。
1.Variable template(变量模板)
C++14 引入了 变量模板(variable templates),让你可以像写函数模板、类模板那样,把 变量本身也做成模板。
通俗地说:以前模板只能生成“函数”或“类”,现在还能生成“变量”。
template <模板形参列表>
变量类型 变量名 = 初始化表达式;
- 模板形参:可以是类型参数
typename T
,也可以是非类型参数int N
等。 - 变量名:实例化后就是真正的全局/命名空间作用域变量。
- 初始化表达式:通常用
constexpr
,让变量在编译期就能求值。
例子:
#include <iostream>
template<typename T>
constexpr T pi_v = T(3.1415926535897932385L); // 变量模板
int main() {
std::cout << pi_v<int> << '\n'; // 3
std::cout << pi_v<double> << '\n'; // 3.14159...
}
非类型参数
template<int N>
int arr_size = N * 2;
int a = arr_size<5>; // a == 10
总结:变量模板让“常量”也能泛型化,是 C++14 中一个简洁却强大的小特性。
2.generic lambdas(通用 Lambda)
C++11的lambda必须写出具体类型
auto add_int = [](int a, int b) { return a + b; };
auto add_float = [](float a, float b) { return a + b; };
类型一变就要重新写一边。
C++14在形参位置直接写 auto
,编译器会为 lambda 生成一个 带模板 operator()
的闭包类。
auto add = [](auto a, auto b) { return a + b; };
等价于手写
struct __lambda_add {
template<typename T, typename U>
auto operator()(T a, U b) const { return a + b; }
};
__lambda_add add{};
完全类型擦除,按需实例化
总结:C++14 Generic Lambda = λ 语法糖 + 模板实例化机制,让你用最少代码写出类型安全的“多态”闭包。
3.lambda init-capture(初始化捕获,又称广义 lambda 捕获)
在 C++11 中,捕获列表只能写两种东西:
[name]
—— 按值或按引用捕获已存在的局部变量。[=]
/[&]
—— 按值或按引用捕获所有可见局部变量。
当有需要捕获移动变量的时候会失败,比如:unique_ptr 只能移动,不能拷贝,[=]
会尝试拷贝(unique_ptr 的拷贝构造函数被删除)
初始化捕获:
[变量名 = 初始化表达式] { ... }
- 初始化表达式可以是任意表达式,包括
std::move
、函数调用结果、字面量等。 - 生成的数据成员直接由该表达式初始化,无需外部变量。
- 因此也支持“无中生有”的捕获:
修复前面的 unique_ptr 例子:
auto p = std::make_unique<int>(42);
auto lam = [p = std::move(p)] { return *p; }; // OK:p 被移动到闭包对象里
总结:Init-Capture 的出现主要是为了解决 “只能移动、不能拷贝的对象无法被 lambda 捕获” 这一根本痛点,并顺带弥补了捕获列表无法当场计算、无法就地声明新变量等表达能力缺陷
4.relaxed restrictions on constexpr functions(放宽限制的constexper)
- 这是 C++14 在编译期编程方面最重要的增强。极大地扩展了可以在
constexpr
函数中使用的功能:- 允许声明局部变量(C++11只能有单个
return
语句)。 - 允许使用
if
和switch
等控制流语句。 - 允许使用任何类型的循环 (
for
,while
,do-while
)。 - 允许修改在函数内部创建的对象(只要该对象的生命周期在
constexpr
函数求值期间开始和结束)。 - 允许函数有多个
return
语句。
- 允许声明局部变量(C++11只能有单个
- 这使得编写更复杂、更实用的编译期计算和类型操作成为可能。
constexpr int factorial(int n) {
if (n <= 0) return 1;
int result = 1; // 允许局部变量
for (int i = 1; i <= n; ++i) { // 允许循环
result *= i;
}
return result;
}
constexpr int fac10 = factorial(10); // 编译期计算
总结:从 C++11 到 C++14,“relaxed restrictions on constexpr functions” 主要把 “只能写一条 return 的纯表达式函数” 放宽成了 “几乎可以写普通 C++ 代码的函数”,从而让编译期计算从“函数式小玩具”变成了“可读写局部变量、有循环、有分支的正经程序”。
5.binary literals(二进制字面量)
没啥好说的,某些情况会用到
- 允许使用
0b
或0B
前缀表示二进制整数常量。
int mask = 0b11010101; // 二进制表示
6.digit separators(数字分隔符)
- 允许在数字常量中使用单引号 (
'
) 作为分隔符以提高长数字的可读性。分隔符对数值没有影响。
long bigNumber = 1'000'000'000;
double pi = 3.14159'26535;
int hexBytes = 0xFF'FF'00'00;
int binMask = 0b1010'1100'0011'1101;
7.return type deduction for functions(函数返回类型推导)
- 允许使用
auto
作为普通函数的返回类型,编译器根据函数体内的return
语句推导返回类型。要求函数的所有return
语句必须推导出相同的类型。 - 简化了模板函数和返回类型复杂的函数的编写。注意: 在类定义中,如果成员函数使用
auto
返回类型且定义在类体内,它需要是inline
的(通常直接在类体内定义即可)。
template
auto sum(T a, U b) {
return a + b;
}
auto getValue(bool flag) {
if (flag) {
return 10; // 返回 int
} else {
return 3.14; // 错误!返回类型不一致 (double vs int)
}
}
8.aggregate classes with default non-static member initializers(聚合体的成员初始化)
- 允许聚合体(如没有用户声明构造函数、私有/保护基类或非静态数据成员的类、结构体、数组)的成员使用大括号或等号初始化器进行初始化。这简化了聚合体的初始化列表构造。
struct Point {
int x;
int y = 10; // C++14 允许成员初始化器
};
Point p1 = {1}; // p1.x = 1, p1.y = 10 (使用默认值)
Point p2 = {2, 20}; // p2.x = 2, p2.y = 20
9.std::make_unique
- 标准库增加了
std::make_unique
模板函数,用于创建std::unique_ptr
对象。这完善了智能指针工具集(std::make_shared
在 C++11 已有),提供更安全、更高效(通常)且更简洁(避免显式new
)的方式来创建独占所有权的对象。非常常用!
// C++11: 需要直接使用 new
std::unique_ptr ptr11(new Widget());
// C++14: 更安全、简洁
auto ptr14 = std::make_unique();
总结:std::make_unique
把 异常安全、代码简洁、避免裸 new
三件事一次搞定,是 C++14 之后创建 unique_ptr
的首选方式;只有在需要自定义删除器或 C++14 里创建动态数组时才退而求其次。
10.std::shared_timed_mutex and std::shared_lock
std::shared_timed_mutex:
- 共享读取:多个线程可以同时获取共享锁(读取锁),允许并发读取数据。
- 独占写入:在写操作时,会独占资源,防止并发写入数据造成数据竞争。
- 超时支持:支持超时等待功能,可以在一定时间内获取锁或放弃操作。
std::shared_lock:
- 自动加锁和解锁:std::shared_lock在构造时自动获取共享锁,在析构时自动释放共享锁,保证锁的正确使用。
- 共享锁支持:std::shared_lock可以管理std::shared_timed_mutex的共享锁,支持多线程并发读取数据。
- 适用于多读单写场景:适用于多个线程读取数据,但需要独占写入数据的场景,提高读操作的并发性能。
总结:std::shared_timed_mutex
是一把支持共享读 / 独占写的可定时互斥量;
std::shared_lock
是专门配合它的 RAII 共享读锁(当然也能配合其他共享互斥量)。
11.std::integer_sequence
在编译期生成一个只包含整数的“不可变数组”,专门用来解包模板参数、推动折叠表达式、实现编译期循环。
模板方面的,跳过看不懂。
12.std::exchange
-
std::exchange
: 原子地将一个新值赋给对象,并返回其旧值。常用于移动赋值运算符等场景。template T exchange(T& obj, U&& new_value) { T old_value = std::move(obj); obj = std::forward(new_value); return old_value; }
13.std::quoted
一句话
把字符串按“带引号、转义内部引号”的格式输入/输出,读写 CSV/JSON/日志时再也不用手工处理引号和反斜杠。