Skip to content

C++14 新特性 ​

C++14 是一个 minor 版本,没有重量级新特性,主要是给 C++11 打补丁、消除使用中的不便:泛型 lambda、初始化捕获、make_unique、放松 constexpr 限制等。本文逐项说明这些「小而关键」的改进,每项附用法与原理。

编译器支持情况见 Compiler support for C++14。系列总览见 现代 C++ 新特性汇总。

语言特性 ​

泛型 lambda ​

lambda 形参可以写 auto,相当于给闭包的 operator() 加了模板:

cpp
auto less_than = [](const auto& a, const auto& b) { return a < b; };

less_than(1, 2);        // int
less_than("a", "b");    // const char*
less_than(1.0, 2);      // 两个参数类型还可以不同

std::sort(v.begin(), v.end(), less_than);   // 直接当通用比较器

原理:编译器把 auto 形参展开成模板形参,每个不同实参组合实例化一份 operator(),与手写模板仿函数等价。

lambda 捕获中使用表达式(初始化捕获) ​

捕获列表里可以「定义新变量」,用表达式初始化,这是 C++20 之前把只移对象搬进 lambda 的唯一办法:

cpp
auto widget = std::make_unique<Widget>(args);

// C++11 做不到:unique_ptr 不能拷贝,也没法按值捕获
auto task = [w = std::move(widget)]() {   // w 是闭包的新成员,由移动构造
    w->run();
};

// 也可以引入任意计算出来的捕获变量
auto counter = [n = 0]() mutable { return ++n; };   // 自带状态的闭包
counter(); counter();   // 返回 1, 2

原理:名字 = 表达式 在闭包类型里生成一个对应类型的成员变量,用表达式初始化——这就是 C++20 之前实现「移动捕获」的标准手法。

函数返回类型推导 ​

函数返回类型可以省略,交给编译器从 return 语句推导:

cpp
auto add(int a, int b) { return a + b; }          // 返回 int

template<typename T, typename U>
auto pick(bool flag, T a, U b) {                  // 模板里终于不用写尾置类型
    return flag ? a : b;                          // 多个 return 必须推导出同一类型
}

限制:多条 return 语句类型必须一致;递归调用要求前置 return 已出现;推导结果默认是值(丢弃引用),这正是引出 decltype(auto) 的原因。

decltype(auto) ​

另一种类型推导:用 decltype 的规则(保留引用和 const)而不是 auto 的规则(丢弃):

cpp
std::vector<int> v{1, 2, 3};

auto a = v[0];             // int:operator[] 返回 int&,auto 丢引用 -> 拷贝了一份
decltype(auto) b = v[0];   // int&:保留引用语义

// 典型场景:转发访问器,保持容器元素的原有引用性
template<typename C>
decltype(auto) at(C& c, std::size_t i) { return c[i]; }
// at(v, 0) = 42;  只有 decltype(auto) 版本能改到 v[0]

注意陷阱:绝不能 decltype(auto) 返回局部变量的引用(悬垂引用)。

放松的 constexpr 限制 ​

constexpr 函数体里允许局部变量、循环、分支,不再限制为单条 return:

cpp
constexpr int factorial(int n) {
    int r = 1;
    for (int i = 2; i <= n; ++i)   // 循环 OK
        r *= i;                    // 可变局部变量 OK
    return r;
}
static_assert(factorial(5) == 120, "");

constexpr int sum_to(int n) {
    if (n < 1) return 0;           // 分支 OK
    return n * (n + 1) / 2;
}

原则没变:编译期求值仍然要求所有路径可确定、无副作用逃逸,放松的只是书写形式,从此 constexpr 函数可以写得和普通函数一样。

变量模板 ​

模板化的变量,为「每个类型一份常量」提供统一写法:

cpp
template<typename T>
constexpr T pi = T(3.1415926535897932385L);

double d = pi<double>;      // double 精度
float  f = pi<float>;       // float 精度

标准库自己用它实现了元函数的值版本(如 C++17 的 std::is_integral_v 一族 _v 变量模板)。

聚合类成员初始化 ​

有默认成员初始化器的类仍然是聚合,可以继续用聚合初始化:

cpp
struct S {
    int i;
    int j = 42;         // C++14:带默认值也还是聚合
};

S s{1};                 // i == 1,j 取默认值 42(C++11 中这直接编译错误)

二进制字面量 ​

0b / 0B 前缀直接写二进制,位掩码不再需要换算:

cpp
unsigned mask_a = 0b0000'0001;     // 位 0(分位符也是 C++14,见下)
unsigned mask_b = 0b0100'0000;     // 位 6
unsigned flags = 0b1010'1010;

数字分位符 ​

单引号 ' 作为数字分隔符,纯粹提高可读性,编译期被丢弃:

cpp
long budget  = 1'000'000'000;
double tiny  = 0.000'000'001;
int hex_mask = 0xFF'FF'FF'FF;
int bin      = 0b1011'0011'0001;   // 与二进制字面量配合使用最为直观

deprecated 属性 ​

[[deprecated]] 标记「还能用但不建议用」,调用处产生编译告警:

cpp
[[deprecated("use new_api() instead")]]
void old_api();

[[deprecated]]                          // 不带理由也行
int legacy_counter();

告警随使用点出现,比注释 "DEPRECATED" 有效得多。

标准库特性 ​

共享的互斥体和锁:shared_timed_mutex ​

读写锁:读操作共享、写操作独占,读多写少场景提升并发度:

cpp
std::shared_timed_mutex rw;

{   // 读端:多个线程可同时持有 shared 锁
    std::shared_lock<std::shared_timed_mutex> rl(rw);
    serve_snapshot();
}
{   // 写端:独占
    std::unique_lock<std::shared_timed_mutex> wl(rw);
    mutate();
}
// "timed" 指带 try_lock_for 等超时接口;C++17 补了不带定时的更轻的 shared_mutex

元函数的别名 ​

给 <type_traits> 里的元函数加上 _t 后缀的类型别名,省掉 typename ...::type:

cpp
// C++11 写法:
typename std::remove_reference<T>::type      x;
// C++14 写法:
std::remove_reference_t<T>                   y;

using Value = std::remove_reference_t<int&>;   // int
static_assert(std::is_same<Value, int>::value, "");

一族别名:remove_const_t、decay_t、conditional_t、enable_if_t、result_of_t 等。C++17 又给 _value 类 trait 加了 _v 变量模板版本。

关联容器中的异构查找 ​

map/set 支持用「可比较的其他类型」做 key 查找,不用构造临时 std::string:

cpp
// 需要透明比较器(无模板参数的 less<>)
std::map<std::string, int, std::less<>> m;

m.find("hello");                  // const char* 直接查找,不构造临时 string
m.find(std::string_view{"hello"});// string_view 也行(C++17 起更常用)

原理:透明比较器内含 is_transparent 类型标记,容器据此启用模板化的 find<K> 重载,用 operator< 互比即可。string_view(C++17)让这个特性的实用性大增。

标准自定义字面量 ​

标准库自带的字面量后缀,统一放在 std::literals 命名空间:

cpp
using namespace std::literals;

auto s  = "hello"s;               // std::string(而不是 const char*)
auto sv = R"(raw)"s + "!"s;       // string 的全套运算符立即可用
auto d  = 1h + 30min + 5s + 100ms;// std::chrono::duration,编译期检查单位

最实用的是时间字面量:std::this_thread::sleep_for(200ms)。

通过类型寻址多元组 ​

std::get 可以用类型而不是下标取 tuple 元素(要求该类型在 tuple 中唯一):

cpp
std::tuple<int, double, std::string> t{1, 2.5, "three"};

auto& s = std::get<std::string>(t);   // 按类型取,不怕改了元素顺序
auto& d = std::get<double>(t);
// std::get<int>(t) 若有两个 int -> 编译错误(必须唯一)

较小的标准库特性 ​

  • std::make_unique:补上 make_shared 的另一半,unique_ptr 也有了安全的工厂函数:

    cpp
    auto p = std::make_unique<Widget>(args);
    
    // 例外安全:C++17 前参数求值顺序未定
    // f(std::unique_ptr<A>(new A), std::unique_ptr<B>(new B));
    // 若第二个 new 先成功、后面的抛异常,A 就泄漏了;make_unique 不会
    f(std::make_unique<A>(), std::make_unique<B>());
  • std::is_final、std::is_null_pointer 等新 trait

  • std::integer_sequence:编译期整数序列,配合变参模板解包,是 std::apply、tuple 展开的实现基础

    cpp
    template<typename T, T... Ints>
    void check(std::integer_sequence<T, Ints...>) {
        // Ints... 在此处可展开为 0, 1, 2, ...
    }
    check(std::make_integer_sequence<int, 3>{});
  • std::exchange:把变量换成新值、返回旧值(移动语义惯用件,swap 内部就用它)

  • std::quoted:流读写带引号的字符串

小结 ​

C++14 的价值在于「补齐 C++11 的欠账」:初始化捕获让 lambda 真正能接管只移资源,放松的 constexpr 让编译期计算可以按普通写法表达,make_unique 填上了智能指针工厂的最后一块缺口。升级成本几乎为零(主流编译器的 C++14 支持早已完备),没有理由停留在 C++11 模式。

系列其他篇目:现代 C++ 新特性汇总 · C++11 新特性 · C++17 新特性 · C++20 新特性 · C++23 新特性

最近更新

基于 VitePress 构建