一、概述
C++14(ISO/IEC 14882:2014)常被称为"C++11 的补丁包"——它没有像 C++11/17/20 那样引入翻天覆地的大特性,而是在 C++11 地基上做了大量精心的小修小补:把 auto 的适用范围拓展到函数返回值、给 lambda 加上泛型参数和 move 捕获、补齐 std::make_unique 这个 C++11 遗憾漏掉的 API。这些改动让现代 C++ 的写法真正变得顺手,是从"C 风格带类"进入"泛型编程 + 函数式范式"不可或缺的过渡版本。
目录
二、核心语言:补全 C++11 体验
2.1 函数返回类型 auto 推导
语言核心C++11 里 auto 只能在变量处推导;到了 C++14,普通函数(非模板特例)的返回值也能写 auto,编译器会根据所有 return 语句统一推导。多个 return 分支类型必须一致。
// C++11 必须写拖尾返回类型,C++14 直接 auto 即可
template<typename T, typename U>
auto add(T a, U b) { // ✅ 编译器根据 a+b 推导返回类型
return a + b; // 注意:所有 return 分支必须类型一致
}
// 实际等价于 C++11:
auto add(T a, U b) -> decltype(a + b) { return a + b; }
auto fib(int n) { // 推导为 int
if (n < 2) return n;
return fib(n-1) + fib(n-2);
}
// 递归函数使用 auto:前向声明仍需推导形式(部分编译器要求先看到返回值语句)
// 常见做法:直接 auto + 让第一个 return 语句写在前面即可
int main() {
std::cout << add(1, 2L) << "\n"; // 返回 long,值 = 3
std::cout << add(1.5, 2.5) << "\n"; // 返回 double,值 = 4.0
}
2.2 泛型 Lambda(参数用 auto)
编程范式lambda 形参现在也可以写 auto,本质上等价于"定义一个匿名的模板 operator()",极大降低了 lambda 处理泛型容器/异构迭代器时的写法成本。
// C++11:写 lambda 前必须显式写参数类型,遇到嵌套模板很啰嗦
std::map<int, std::vector<std::string>> mp;
// C++11:手写 pair 类型
std::for_each(mp.begin(), mp.end(),
[](const std::pair<const int, std::vector<std::string>>& kv){ /* kv.second ... */ });
// C++14 泛型 Lambda:auto 直接做形参,编译器帮你生成模板
std::for_each(mp.begin(), mp.end(),
[](const auto& kv){ /* kv.second ... */ });
// 结合函数式管道:一个 lambda 既能吃 double,也能吃 complex、Matrix
auto twice = [](auto x) { return x + x; };
twice(21); // int → 42
twice(3.14); // double → 6.28
twice(std::string("ab")); // string → "abab"
// 进阶:多参数泛型 Lambda 做任意可相加类型的 sum
auto sumAll = [](auto... xs) { return (xs + ...); }; // 搭配 C++17 折叠表达式更漂亮(此处仅演示泛型)
2.3 Lambda 初始化捕获(广义捕获 / Move Capture)
语言核心C++11 只能"值 / 引用"二选一;C++14 允许在 [a = expr, &b = refexpr] 中用任意表达式初始化捕获变量,从而实现移动捕获——把 std::unique_ptr 这种不可拷贝的对象直接"搬进"lambda 里,彻底解决 C++11 的痛点。
#include <memory>
using namespace std::chrono_literals; // 顺带:C++14 新增 1s / 200ms 字面量(也属于小改进)
int main() {
// (1) 基本初始化捕获:创建一个全新的捕获变量
int base = 10;
auto f = [bonus = base * 2](int x) { return x + bonus; }; // bonus 捕获时计算一次 =20
f(3); // → 23
// (2) 移动捕获:把不可拷贝的 unique_ptr 搬进 lambda(C++11 做不到!)
auto pw = std::make_unique<int>(42);
auto consumer = [p = std::move(pw)]() { // p 是 lambda 内部成员,从 pw 移入
std::cout << "captured value: " << *p << "\n";
};
consumer(); // "captured value: 42"
// pw 在外部已被移空,不再拥有对象
// (3) 把大对象 move 进异步任务(常见工程用法)
auto bigBuf = std::make_shared<std::vector<double>>(1<<20); // 4MB 数据
auto fut = std::async(std::launch::async,
[data = std::move(bigBuf)]() {
// 后台线程对 data 做计算,避免共享内存读写
double sum = 0.0;
for (auto v : *data) sum += v;
return sum;
});
}
2.4 变量模板 Variable Templates
模板元编程C++11 只能做"类模板 / 函数模板 / 类型别名模板",要做一个随模板参数变化的常量只能写成类的静态成员。C++14 直接允许 template<T> constexpr T pi = T(3.1415926535...);,也是 C++17 is_same_v / is_integral_v 等 `_v` 后缀变量模板的底层基础。
#include <iostream>
#include <type_traits>
#include <iomanip>
/* 变量模板:一套模板参数 → 对应一个可变或常量"变量" */
template<typename T>
constexpr T pi = T(3.141592653589793238462643);
/* 经典 C++11 的写法:只能包一层 struct(啰嗦)*/
template<typename T> struct OldPi { static constexpr T value = T(3.1415926535); };
int main() {
// 想用什么精度就实例化什么精度(再也不用 pi<T>::value)
std::cout << std::setprecision(12)
<< pi<float> << '\n' // 3.14159274101 (float, ~7 位有效)
<< pi<double> << '\n' // 3.14159265359 (double)
<< pi<long double> << '\n'; // 更高精度
/* 真正大规模应用:type_traits 的 _v 后缀简写(C++17 正式标准化,C++14 已可自己写)*/
template<typename T, typename U>
constexpr bool is_same_v = std::is_same<T, U>::value; // ← C++14 自己定义
static_assert(is_same_v<int, int>, "ooops");
// static_assert(is_same_v<int, long>, ...); // ❌ 编译错误
}
2.5 constexpr 函数限制大幅放开
编译期计算C++11 的 constexpr 函数几乎"只能一条 return 语句"(可用递归来绕)。C++14 直接允许内部有局部变量、循环、分支、局部 constexpr 变量,写编译期计算终于和写运行期函数感受一致。
/* C++11 constexpr 函数:几乎只能用三目运算符 + 递归,写起来非常反人类
template<int N>
constexpr long long factorial11() { return N <= 1 ? 1 : N * factorial11<N-1>(); } */
// C++14 constexpr:终于可以写 if / for / 局部变量了!
constexpr long long factorial(int n) {
long long res = 1; // ✅ 允许声明局部变量
for (int i = 2; i <= n; ++i) { // ✅ 允许普通 for 循环(递归也行)
res *= i;
}
return res;
}
constexpr int gcd(int a, int b) {
while (b != 0) { // ✅ 允许 while 循环
int tmp = a % b;
a = b;
b = tmp;
}
return a;
}
// 编译期直接求值 → 编译期常量
static_assert(factorial(5) == 120, "5! should be 120");
static_assert(gcd(48, 18) == 6, "gcd(48,18)=6");
// 也能在运行期调用(输入不是常量表达式时,降级为普通函数)
int main() {
int n; std::cin >> n;
std::cout << factorial(n) << '\n'; // 同一份函数源码,运行期也能用
}
2.6 decltype(auto)
泛型转发把 C++11 "拖尾返回类型 + decltype"的组合简化成一个返回类型关键词。在完美转发函数里极其常用:你想原样返回内部表达式的声明类型(保留引用 / 值类别),就用它。auto 会丢引用(默认按值推导),decltype(auto) 不会。
/* 经典应用场景:通用的"透明"包装函数
目标:不管内部函数返回 T / T& / T&& / const T&,外层都丝毫不改地转出去 */
template<typename Func, typename... Args>
decltype(auto) invoke_and_forward(Func f, Args&&... args) {
return f(std::forward<Args>(args)...); // 完整保留 f 的返回值类型 / 引用
}
int g1() { return 0; }
int& g2(int& x) { return x; }
int&& g3() { return std::move(int{42}); }
int main() {
auto a = invoke_and_forward(g1); // int
int n = 10;
decltype(auto) b = invoke_and_forward(g2, n); // int&(绑定到 n)
b = 100; // 修改的其实是 n,n 变 100
/* 不用 decltype(auto) 会怎样?用 auto 丢引用 */
// auto 包装版本:返回的是拷贝(不会再绑定到原对象)
}
2.7 [[deprecated]] 属性 + 二进制字面量 + 数字分隔符
语法糖[[deprecated("原因")]]:正式标准化"标记废弃 API"的写法,跨编译器统一给出警告。
0b / 0B:与 Python/Java 一致的二进制整数前缀。' 单引号作为数字分隔符:十亿级数字终于能按千分组。
// 1) [[deprecated]]:废弃函数、枚举、类、变量皆可
[[deprecated("请使用 newCalc(),旧实现有数值稳定性问题")]]
int oldCalc(int x, int y) { return x / y; }
struct [[deprecated("Use NewProtocol instead")]] OldProtocol {};
int main() {
// oldCalc(10, 3); // ✅ 编译仍通过,但编译器警告 + 输出上方自定义原因消息
// 2) 0b 二进制字面量:做位掩码的时候可读性飙升
uint8_t mask = 0b10110010; // = 0xB2 = 178
int kb100 = 0b11000011010100000; // 也能写 100KB = 102400
// 3) ' 单引号数字分隔符(跨进制通用)
long long usa_gdp = 26'000'000'000'000LL; // 26 万亿,一眼可读
unsigned hex_addr = 0xDEAD'BEEF; // 十六进制 32 位地址
unsigned bin_mask2 = 0b1101'1010'0011'1100; // 4 位一组,硬件寄存器配置常用
double pi_digit = 3.141'592'653'589; // 浮点小数也能加分隔符
}
三、标准库升级(小而精)
3.1 std::make_unique(C++11 最大遗憾的修复)
资源安全C++11 有 make_shared,却因为标准委员会时间不够,意外漏掉了 make_unique,导致很多团队只能自己写 unique_ptr<T>(new T(args...))。C++14 正式补全,从此 new/delete 不再需要出现在应用代码里。
#include <memory>
struct Point { int x, y; Point(int a, int b) : x(a), y(b) {} };
int main() {
// C++11 的旧写法(不推荐:手写 new,异常不安全当组合表达式)
std::unique_ptr<Point> p1(new Point(1, 2));
// C++14 标准写法:make_unique 完美转发参数 + 内部 new + 异常安全
auto p2 = std::make_unique<Point>(3, 4);
// 对数组的部分支持(不调用构造函数/析构,长度固定分配)
auto buf = std::make_unique<double[]>(1 << 16); // 64K 个 double(= 512KB)
buf[0] = 3.14;
}
3.2 std::integer_sequence / index_sequence(元编程基石)
模板元编程标准库提供一个"编译期整数序列"的小工具,本质是 template<size_t... I> struct index_sequence {}。它是展开 tuple 做"apply(把 tuple 当参数包)"、把元组元素展开到函数实参列表等泛型编程操作的基础组件。C++17 把它进一步封装为 std::apply。
#include <tuple>
#include <utility> // index_sequence / make_index_sequence
#include <iostream>
void print3(int a, const std::string& b, double c) {
std::cout << a << ", " << b << ", " << c << "\n";
}
// 使用 index_sequence 把 tuple 元素按顺序展开为函数参数包
template<typename Fn, typename Tuple, size_t... I>
decltype(auto) apply_impl(Fn f, Tuple&& t, std::index_sequence<I...>) {
return f(std::get<I>(std::forward<Tuple>(t))...);
}
template<typename Fn, typename Tuple>
decltype(auto) myApply(Fn f, Tuple&& t) {
// make_index_sequence<N> 自动生成 0,1,..,N-1 的整数序列
using Indices = std::make_index_sequence<std::tuple_size<typename std::decay<Tuple>::type>::value>;
return apply_impl(std::forward<Fn>(f), std::forward<Tuple>(t), Indices{});
}
int main() {
auto t = std::make_tuple(42, "hello", 3.14);
myApply(print3, t); // → print3(42, "hello", 3.14) 输出:42, hello, 3.14
}
3.3 std::shared_timed_mutex(读写锁雏形)
并发库提供"读共享、写独占"的 timed 读写锁,搭配 shared_lock(读端)/unique_lock(写端)。C++17 在其基础上新增了 shared_mutex(无 timed,更高效)。典型使用场景:配置更新频率低,但读线程极多。
#include <shared_mutex> // C++14:shared_timed_mutex 在这个头里
#include <unordered_map>
#include <string>
class Config {
mutable std::shared_timed_mutex mtx_;
std::unordered_map<std::string, std::string> kv_;
public:
// 读:多线程共享锁(高并发,互不阻塞)
std::string get(const std::string& key) const {
std::shared_lock<std::shared_timed_mutex> read(mtx_);
auto it = kv_.find(key);
return it == kv_.end() ? "" : it->second;
}
// 写:互斥独占(全局只有一个写者)
void set(const std::string& key, const std::string& value) {
std::unique_lock<std::shared_timed_mutex> write(mtx_);
kv_[key] = value;
}
};
3.4 std::exchange / std::quoted / 算法库增强
小而美exchange(t, new_val):把 t 的旧值返回并赋新值,是 move 类、迭代器实现中极其常见的小工具(C++11 只能手写两行)。quoted(s):给字符串加引号方便 IO round-trip(读入再输出能恢复原样空格/引号)。算法库新增 equal / mismatch / is_permutation 的双范围版本。
#include <utility> // exchange
#include <iomanip> // quoted
#include <sstream>
#include <vector>
#include <algorithm>
#include <cassert>
int main() {
/* 1) exchange:经典使用是自定义移动构造中"偷值" */
int a = 10;
int old = std::exchange(a, 99); // 等价于:int old = a; a = 99; return old;
// old = 10, a = 99
assert(old == 10 && a == 99);
/* 2) std::quoted:IO 中字符串含空格、引号时安全序列化 */
std::string origin = R"(Yang Chao "Engineer" 'C++')";
std::ostringstream oss;
oss << std::quoted(origin); // 序列化后:"Yang Chao \"Engineer\" 'C++'"(自动加外引号并转义)
// 读回:自动去外引号 + 还原内部转义
std::istringstream iss(oss.str());
std::string restored;
iss >> std::quoted(restored);
assert(restored == origin); // 原始字符串完整无损恢复
/* 3) 算法:双范围 equal(C++11 只支持长度相同的两序列,C++14 支持长度独立传第二个末迭代器)*/
std::vector<int> v1 = {1,2,3};
std::vector<int> v2 = {1,2,3,4};
// C++11:equal(v1.begin(), v1.end(), v2.begin()) // ❌ v2 更长不会检测(越界风险)
bool same = std::equal(v1.begin(), v1.end(),
v2.begin(), v2.end()); // ✅ 安全:传两个 end,长度不同直接返回 false
assert(same == false);
}
四、C++11 → C++14 特性总览对照表
| 分类 | 特性 | 一句话价值 |
|---|---|---|
| 核心语言 | 函数返回 auto 推导 | 模板函数不用再写拖尾 decltype |
| 泛型 Lambda(auto 形参) | 匿名模板仿函数 | |
| Lambda 初始化捕获 + Move Capture | unique_ptr 等不可拷贝对象也能进 lambda | |
| Variable Templates 变量模板 | type_traits _v 后缀成为可能 | |
| constexpr 函数放宽限制 | 编译期计算和运行期函数写起来一样 | |
| decltype(auto) | 完美转发返回值的引用性 | |
| [[deprecated]] / 0b / ' 数字分隔符 | 跨平台弃用警告 + 字面值可读性 | |
| 标准库 | make_unique | 补全 C++11 缺口,全面替换裸 new |
| index_sequence / integer_sequence | tuple 展开等模板元编程基础工具 | |
| shared_timed_mutex(读写锁) | 多读少写场景的并发控制 | |
| exchange / quoted / 双范围 equal | 工程上的小工具补齐 |
总结 & 升级建议
- 无脑开启:所有项目可直接从 C++11 升级到 C++14,几乎没有破坏性变更。编译器 GCC 5 / Clang 3.4 / VS2015 起全线支持。
- 重点应用 1:把所有手写 unique_ptr(new T) 改为 make_unique;函数返回、lambda 参数全部开启 auto 泛型。
- 重点应用 2:并发多读少写场景用 shared_timed_mutex 替换 mutex(C++17 升级到 shared_mutex)。
- 作为 C++17 的跳板:C++14 许多"小特性"(变量模板、index_sequence、泛型 lambda)都是 C++17 中 Ranges / 并行算法 / std::apply 的底层构建块。