C++14
C++14是C++的现行标准的非正式名称,正式名称为"International Standard ISO/IEC 14882:2014(E) Programming Language C++"。C++14旨在作为C++11的一个小扩展,主要提供漏洞修复和小的改进。C++14标准的委员会草案()N3690于2013年5月15日发表。[1]工作草案()N3936已于2014年3月2日完成。最终的投票期结束于2014年8月15日,结果(一致通过)已于8月18日公布。[2]
新的语言特性
以下为在C++14中被加入语言核心的特性。
泛型的lambda
在C++11中,lambda函数的形式参数需要被声明为具体的类型。C++14放宽了这一要求,允许lambda函数的形式参数声明中使用类型说明符auto
。[3]
auto lambda = [](auto x, auto y) {return x + y;}
泛型lambda函数遵循模板参数推导的规则。以上代码的作用与下面的代码相同:[4]
struct unnamed_lambda
{
template<typename T, typename U>
auto operator()(T x, U y) const {return x + y;}
};
auto lambda = unnamed_lambda{};
Lambda捕获部分中使用表达式
C++11的lambda函数通过值拷贝()或引用()捕获()已在外层作用域声明的变量。这意味着lambda捕获的变量不可以是move-only的类型。[5]
C++14允许lambda成员用任意的被捕获表达式初始化。这既允许了capture by value-move,也允许了任意声明lambda的成员,而不需要外层作用域有一个具有相应名字的变量。[6]这称为广义捕获(Generalized capture)。[7]即在捕获子句(capture clause)中增加并初始化新的变量,该变量不需要在lambda表达式所处的闭包域(enclosing scope)中存在;即使在闭包域中存在也会被新变量覆盖(override)。新变量类型由它的初始化表达式推导。用途是可以从外层作用域中捕获只供移动的变量并使用它。
也即,允许创建lambda捕获并用任意表达式初始化,捕获中的名字不需要一定出现在闭包域,初始化表达式在lambda被创建(而不是被调用)时求值。 使用引用捕获可以对被引用的外部闭包的变量使用别名。
auto x = 1;
auto f = [&r = x, x = x * 10] {
++r;
return r + x;
};
f(); // sets x to 2 and returns 12
这是通过使用一个初始化表达式完成的:
auto lambda = [value = 1] {return value;}
lambda函数lambda
的返回值是1,说明value
被初始化为1。被声明的捕获变量的类型会根据初始化表达式推断,推断方式与用auto
声明变量相同。
使用标准函数std::move
可以使之被用以通过move捕获:
auto ptr = std::make_unique<int>(10); //See below for std::make_unique
auto lambda = [value = std::move(ptr)] {return *value;}
函数返回类型推导
C++11允许lambda函数根据return语句的表达式类型推断返回类型。C++14为一般的函数也提供了这个能力。C++14还拓展了原有的规则,使得函数体并不是{return expression;}
形式的函数也可以使用返回类型推导。[8]
为了启用返回类型推导,函数声明必须将auto
作为返回类型,但没有C++11的后置返回类型说明符:
auto DeduceReturnType(); //返回类型由编译器推断
如果函数实现中含有多个return语句,这些表达式必须可以推断为相同的类型。[9]
使用返回类型推导的函数可以前向声明,但在定义之前不可以使用。它们的定义在使用它们的翻译单元()之中必须是可用的。
这样的函数中可以存在递归,但递归调用必须在函数定义中的至少一个return语句之后:[9]
auto Correct(int i) {
if (i == 1)
return i; // 返回类型被推断为int
else
return Correct(i-1)+i; // 正确,可以调用
}
auto Wrong(int i)
{
if(i != 1)
return Wrong(i-1)+i; // 不能调用,之前没有return语句
else
return i; // 返回类型被推断为int
}
另一种类型推断
C++11中有两种推断类型的方式。auto
根据给出的表达式产生具有合适类型的变量。decltype
可以计算给出的表达式的类型。但是,decltype
和auto
推断类型的方式是不同的。特别地,auto
总是推断出非引用类型,就好像使用了std::remove_reference
一样,而auto&&
总是推断出引用类型。然而decltype
可以根据表达式的值类别()和表达式的性质推断出引用或非引用类型:[8]
int i;
int&& f();
auto x3a = i; // x3a的类型是int
decltype(i) x3d = i; // x3d的类型是int
auto x4a = (i); // x4a的类型是int
decltype((i)) x4d = (i); // x4d的类型是int&
auto x5a = f(); // x5a的类型是int
decltype(f()) x5d = f(); // x5d的类型是int&&
C++14增加了decltype(auto)
的语法。允许auto的类型声明使用decltype的规则。也即,允许不必显式指定作为decltype
参数的表达式,而使用decltype
对于给定表达式的推断规则。
const int x = 0;
auto x1 = x; // int
decltype(auto) x2 = x; // const int
int y = 0;
int& y1 = y;
auto y2 = y1; // int
decltype(auto) y3 = y1; // int&
int&& z = 0;
auto z1 = std::move(z); // int
decltype(auto) z2 = std::move(z); // int&&
decltype(auto)
的语法也可以用于返回类型推导,只需用decltype(auto)
代替auto
。[9]
// Return type is `int`.
auto f(const int& i) {
return i;
}
// Return type is `const int&`.
decltype(auto) g(const int& i) {
return i;
}
int CPP()
{
// Note: Especially useful for generic code!
int x = 123;
static_assert(std::is_same<const int&, decltype(f(x))>::value == 0);
static_assert(std::is_same<int, decltype(f(x))>::value == 1);
static_assert(std::is_same<const int&, decltype(g(x))>::value == 1);
return 0;
}
放松的constexpr函数限制
C++11引入了声明为constexpr的函数的概念。声明为constexpr函数的意义是:如果其参数均为合适的编译期常量,则对这个constexpr函数的调用就可用于期望常量表达式的场合(如模板的非类型参数,或枚举常量的值)。如果参数的值在运行期才能确定,或者虽然参数的值是编译期常量,但不符合这个函数的要求,则对这个函数调用的求值只能在运行期进行。
然而C++11要求constexpr函数只含有一个将被返回的表达式(也可以还含有static_assert
声明等其它语句,但允许的语句类型很少)。
C++14放松了这些限制。声明为constexpr的函数可以含有以下内容:[8]
- 任何声明,除了:
static
或thread_local
变量。- 没有初始化的变量声明。
- 条件分支语句
if
和switch
。 - 所有的循环语句,包括基于范围的
for
循环。 - 表达式可以改变一个对象的值,只需该对象的生命期在声明为constexpr的函数内部开始。包括对有
constexpr
声明的任何非const
非静态成员函数的调用。
goto
仍然不允许在constexpr函数中出现。
此外,C++11指出,所有被声明为constexpr
的非静态成员函数也隐含声明为const
(即函数不能修改*this的值)。这点已经被删除,非静态成员函数可以为非const
。[10]
变量模板
在C++之前的版本中,模板可以是函数模板或类模板(C++11引入了类型别名模板)。C++14现在也可以创建变量模板。在提案中给出的示例是变量pi
,其可以被读取以获得各种类型的pi
的值(例如,当被读取为整数类型时为3;当被读取为float
,double
,long double
时,可以是近似float
,double
或long double
精度的值)包括特化在内,通常的模板的规则都适用于变量模板的声明和定义。[6][11]
template<typename T>
constexpr T pi = T(3.141592653589793238462643383);
// 适用于特化规则 :
template<>
constexpr const char* pi<const char*> = "pi";
聚合类成员初始化
C++11增加了default member initializer,如果构造函数没有初始化某个成员,并且这个成员拥有default member initializer,就会用default member initializer来初始化成员。聚合类(aggregate type)的定义被改为明确排除任何含有default member initializer的类类型,因此,如果一个类含有default member initializer,就不允许使用聚合初始化。
C++14放松了这一限制,[8]含有default member initializer的类型也允许聚合初始化。如果在定义聚合体类型的对象时,使用的花括号初始化列表没有指定该成员的值,将会用default member initializer初始化它。[12]
struct CXX14_aggregate {
int x;
int y = 42;
};
CXX14_aggregate a = {1}; // C++14允许。a.y被初始化为42
数字分位符
C++14引入单引号(')作为数字分位符号,使得数值型的字面量可以具有更好的可读性。[13]
Ada、D语言、Java、Perl、Ruby等程序设计语言使用下划线(_)作为数字分位符号,C++之所以不和它们保持一致,是因为下划线已被用在用户自定义的字面量的语法中。
auto integer_literal = 100'0000;
auto floating_point_literal = 1.797'693'134'862'315'7E+308;
auto binary_literal = 0b0100'1100'0110;
auto silly_example = 1'0'0'000'00;
deprecated 属性
deprecated
属性允许标记不推荐使用的实体,该实体仍然能合法使用,但会让用户注意到使用它是不受欢迎的,并且可能会导致在编译期间输出警告消息。 deprecated
可以有一个可选的字符串文字作为参数,以解释弃用的原因和/或建议替代者。
[[deprecated]] int f();
[[deprecated("g() is thread-unsafe. Use h() instead")]]
void g( int& x );
void h( int& x );
void test() {
int a = f(); // 警告:'f'已弃用
g(a); // 警告:'g'已弃用:g() is thread-unsafe. Use h() instead
}
新的标准库特性
共享的互斥体和锁
C++14增加了一类共享的互斥体和相应的共享锁[14][15]。起初选择的名字是std::shared_mutex
,但由于后来增加了与std::timed_mutex
相似的特性,std::shared_timed_mutex
成为了更适合的名字。[16]
元函数的别名
C++11定义了一组元函数,用于查询一个给定类型是否具有某种特征,或者转换给定类型的某种特征,从而得到另一个类型。后一种元函数通过成员类型type来返回转换后的类型,当它们用在模板中时,必须使用typename关键字,这会增加代码的长度。
template <class T>
type_object<
typename std::remove_cv<
typename std::remove_reference<T>::type
>::type
>get_type_object(T&);
利用类型别名模板,C++14提供了更便捷的写法。其命名规则是:如果标准库的某个类模板(假设为std::some_class
)只含有唯一的成员,即成员类型type,那么标准库提供std::some_class_t<T>
作为typename std::some_class::type
的别名。
在C++14,拥有类型别名的元函数包括:remove_const、remove_volatile、remove_cv、add_const、add_volatile、add_cv、remove_reference、add_lvalue_reference、add_rvalue_reference、make_signed、make_unsigned、remove_extent、remove_all_extents、remove_pointer、add_pointer、aligned_storage、aligned_union、decay、enable_if、conditional、common_type、underlying_type、result_of、tuple_element。
template <class T>
type_object<std::remove_cv_t<std::remove_reference_t<T>>>
get_type_object(T&);
关联容器中的异构查找
C++标准库定义了四个关联容器类。set和multiset允许使用者根据一个值在容器中查找对应的同类型的值。map和multimap容器允许使用者指定键(key)和值(value)的类型,根据键进行查找并返回对应的值。然而,查找只能接受指定类型的参数,在map和multimap中是键的类型,而在set和multiset容器中就是值本身的类型。
C++14允许通过其他类型进行查找,只需要这个类型和实际的键类型之间可以进行比较操作。[17]这允许std::set<std::string>
使用const char*
,或任何可以通过operator<
与std::string
比较的类型作为查找的参数。
为保证向后兼容性,这种异构查找只在提供给关联容器的比较器允许的情况下有效。标准库的泛型比较器,如std::less<>
与std::greater<>
允许异构查找。[18]
标准自定义字面量
C++11增加了自定义字面量()的特性,使用户能够定义新的字面量后缀,但标准库并没有对这一特性加以利用。C++14标准库定义了以下字面量后缀:[17]
- "s",用于创建各种
std::basic_string
类型。 - "h"、"min"、"s"、"ms"、"us"、"ns",用于创建相应的
std::chrono::duration
时间间隔。 - "if"、"i"、"il"用于创建相应的
std::complex<float>
、std::complex<double>
和std::complex<long double>
复数类型。
这些字面量可用于编译时的constexpr。
auto str = "hello world"s; // 自动推导为 std::string
auto dur = 60s; // 自动推导为 chrono::seconds
auto z = 1i; // 自动推导为 complex<double>
两个"s"互不干扰,因为表示字符串的只能对字符串字面量操作,而表示秒的只针对数字。[19]
通过类型寻址多元组
C++11引入的std::tuple
类型允许不同类型的值的聚合体用编译期整型常数索引。C++14还允许使用类型代替常数索引,从多元组中获取对象。[17]若多元组含有多于一个这个类型的对象,将会产生一个编译错误:[20]
tuple<string, string, int> t("foo", "bar", 7);
int i = get<int>(t); // i == 7
int j = get<2>(t); // Same as before: j == 7
string s = get<string>(t); //Compiler error due to ambiguity
已被移除或是不包含在C++14标准的特性
因为C++14的主要目的是漏洞修复和小的改进,一些重量级的特性被从C++14中移除,其中有部分将加入C++17标准。
关于数组的扩展
在C++11和之前的标准中,在堆栈上分配的数组被限制为拥有一个固定的、编译期确定的长度。这一扩展允许在堆栈上分配的一个数组的最后一维具有运行期确定的长度。[6]
运行期确定长度的数组不可以作为对象的一部分,也不可以具有全局存储期,他们只能被声明为局部变量。运行期确定长度的数组也可以使用C++11的基于范围的for循环。[21]
同时还将添加std::dynarray
类型,它拥有与std::vector
和std::array
相似的接口。代表一个固定长度的数组,其大小在运行期构造对象时确定。std::dynarray
类被明显地设计为当它被放置在栈上时(直接放置在栈上,或作为另一个栈对象的成员),可以使用栈内存而不是堆内存。
由于一些设计无法达成一致,这一扩展已被放弃。
Optional值
类似于C#中的可空类型,optional类型可能含有或不含有一个值。这一类型基于Boost的boost::optional
类,而添加了C++11和C++14中的新特性,诸如移动和in-place构造。它不允许用在引用类型上。这个类被专门的设计为一个literal type(如果模板参数本身是一个literal type),因此,它在必要的情况下含有constexpr构造函数。[22]
Concepts Lite
被C++11拒绝后,Concepts受到彻底的修改。Concepts Lite是Concepts的一个部分,仅包含类型约束,而不含concept_map
和axiom
[23]。 ISO/IEC TS 19217:2015 Information technology -- Programming languages -- C++ Extensions for concepts已出版。
参考资料
- (PDF). [2013-07-25]. (原始内容存档 (PDF)于2013-08-07).
- Sutter, Herb. . August 18, 2014 [2014-08-18]. (原始内容存档于2014-08-19).
- josedaniel. . [2014-09-20]. (原始内容存档于2014-09-20).
- . [2013-07-25]. (原始内容存档于2013-08-25).
- . [2013-07-25]. (原始内容存档于2013-01-24).
- Sutter, Herb. . [2013-06-14]. (原始内容存档于2017-08-20).
- MSDN: In C++14, you can introduce and initialize new variables in the capture clause, without the need to have those variables exist in the lambda function’s enclosing scope. The initialization can be expressed as any arbitrary expression; the type of the new variable is deduced from the type produced by the expression. One benefit of this feature is that in C++14 you can capture move-only variables (such as std::unique_ptr) from the surrounding scope and use them in a lambda.
- Wong, Michael. . C/C++ Cafe. [14 June 2013]. (原始内容存档于2013-10-13).
- . [14 June 2013]. (原始内容存档于2013-08-25).
- . [2013-07-25]. (原始内容存档于2013-08-25).
- (PDF). [2013-07-25]. (原始内容存档 (PDF)于2013-08-25).
- . [2013-07-25]. (原始内容存档于2013-08-25).
- Crowl, Lawrence; Smith, Richard; Snyder, Jeff; Vandevoorde, Daveed. (PDF). 25 September 2013 [2014-04-11]. (原始内容存档 (PDF)于2014-04-13).
- Wong, Michael. . C/C++ Cafe. [14 June 2013]. (原始内容存档于2013-10-13).
- . [2013-07-25]. (原始内容存档于2013-08-19).
- . [2014-04-11]. (原始内容存档于2014-04-13).
- Wong, Michael. . C/C++ Cafe. [14 June 2013]. (原始内容存档于2013-10-13).
- . [2013-07-25]. (原始内容存档于2013-08-19).
- (PDF). [2013-07-25]. (原始内容存档 (PDF)于2013-08-25).
- . [2013-07-25]. (原始内容存档于2013-08-19).
- . [2013-06-14]. (原始内容存档于2013-06-24).
- . [2013-07-25]. (原始内容存档于2013-08-25).
- (pdf). 2013-03-17 [2014-06-26]. (原始内容存档 (PDF)于2013-11-10).