← 返回博客列表

一、概述

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 Captureunique_ptr 等不可拷贝对象也能进 lambda
Variable Templates 变量模板type_traits _v 后缀成为可能
constexpr 函数放宽限制编译期计算和运行期函数写起来一样
decltype(auto)完美转发返回值的引用性
[[deprecated]] / 0b / ' 数字分隔符跨平台弃用警告 + 字面值可读性
标准库make_unique补全 C++11 缺口,全面替换裸 new
index_sequence / integer_sequencetuple 展开等模板元编程基础工具
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 的底层构建块。