C++ 标准从 C++11 开始,采用一个大版本后接一个小版本的更新思路,所以 C++14 是一个小版本更新的标准,它的主要目的是完善 C++11 标准。

一些 C++11 标准发布时还不稳定的特性,在 C++14 中被加入标准中。另外,这么做也是给编译器实现者们提供一个缓冲,在 C++11 标准实现之后的晚一些时间,提出一些小型的完善性的提案。

为了能完整的使用现代 C++ 特性,通常不会开启 C++11,而是会选择开启 C++14。


本文是 C++ 特性杂谈系列文章的第二篇:

  1. C++11 特性杂谈
  2. C++14 特性杂谈
  3. C++17 特性杂谈
  4. C++20 特性杂谈

数字分隔符

数字分隔符是 C++14 中的一个小特性,但却引起了很大的讨论。讨论的根源在于,使用什么分隔符可以最大程度上既能有效表示分隔符的意义,同时还能兼容旧代码。

1
auto num = 1'234'456s;  // 1234456 秒

最终选定的分隔符是 ‘ ,这在我看来并不是一个特别好的决定,’ 用来包裹字符,虽然从语义上讲,数字分隔符不可能出现在一串数字的最前边,所以不会与字符产生冲突,但依然容易让人有困扰。

只能说,感谢静态分析工具和语法高亮吧。


变量模板

坦白说,我没有在哪里看到过这个特性的用途。它用于定义一个带有模板参数的变量(而不是函数),对定义的表达式做泛化。

1
2
3
4
5
6
7
8
template<typename T>
constexpr T pi = T(3.14159);

template<typename T>
T circular_area(T r) {
return pi<T> * r * r;
}

用途可能比较少,不过在 C++20 中定义 concept 时开始变地常用。

补充:C++17 中,变量模版被用于在标准库中为类型特征模版实现对应的变量模版实现。比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
// C++17 之前,我们可能写出这样的代码:
#include <type_traits>

if (std::is_integral<T>::value) {
// ...
}

// C++17 及之后,可以替换为:
#include <type_traits>

if (std::is_integral_v<T>) { // 等价于上一句,但更简洁
// ...
}

也就是使用 std::XXX_v<T> 来取代之前的 std::XXX<T>::value,他们的区别是,前者是一个变量模版,后者是对一个类模版中静态成员 value 的引用。这个例子中,变量模版的实现简单来说是:

1
2
3
4
namespace std {
template< class T >
inline constexpr bool is_integral_v = is_integral<T>::value;
}

感谢 @小蔡蔡 的补充。


函数返回值类型推导

C++11 中的 lambda 函数,可以不写返回值类型。编译器可以通过 return 位置的类型来推导返回值类型。这种技术显然也可以应用在普通函数中,所以 C++14 中增加了这个特性,函数的返回类型为 auto。

1
2
3
template<typename T>
auto size(const T& a) { return a.size(); }

如果多个不同的 return 位置推导出了不同的类型,编译时会报错。

但这种推导返回值的函数会产生一个限制:函数签名是不稳定的,它由它的定义所决定。所以在调用这种函数时,函数定义必须已经可见(不能只给声明)。


泛型 lambda 表达式

泛型的 lambda 表达式在 C++11 提出时就已经经过了广泛的讨论,但最后认为还没有准备好,故没有出现在 C++11 标准中。因为 lambda 表达式本质上也是一种对象类型,所以支持泛型是显然合理的。

1
auto get_size = [](auto &m) { return m.size(); }

使用 auto 来作为标记,指出类型是通过推导得出的。


增强的 constexpr

constexpr 特性是 C++ 编译期编程的基础。

C++11 中的 constexpr 函数是要求比较严格的,只允许对纯函数的表达式进行求值,后来有一些人对此提出新的要求。在 C++14 中,可以允许 constexpr 函数中包含局部变量和循环,前提是它们不会对函数外部产生副作用。这个改进让编写 constexpr 函数更加自由灵活。

这些局部变量仅对编译器可见,编译器在编译时就会对函数求值。

在后续的 C++ 标准中,constexpr 的用法被进一步放宽,这得益于编译器实现的完善。尽可能将更多的计算从运行期移动到编译期是 C++ 设计的其中一个倾向,也即在最大程度上保证 C++ 编译的程序在运行时的性能。


make_unique

std::make_shared 是一个 C++11 标准接口,它可以使得在创建一个 shared_ptr 对象时,不需要显式的暴露出来 new 关键字。但可惜的是,make_unique 没有出现在 C++11 中,而在 C++14 中才被加入标准库。在 C++11 中,只能自己编写一个 make_unique。

对我自己来说,这是 C++14 中最常用的一个特性。


本文同步发布在知乎账号下:C++14 特性杂谈 - 知乎 (zhihu.com)

封面图片是一只海鸥,来自:Photo by Carl Newton on Unsplash