C++20 包括以下新的语言功能:
C++20 包括以下新库功能:
C++17 包括以下新的语言功能:
C++17 包含以下新库功能:
C++14 包括以下新的语言功能:
C++14 包含以下新库功能:
C++11 包括以下新的语言功能:
C++11 包含以下新库功能:
注意:虽然这些示例说明了如何在基本级别上使用协程,但编译代码时还会发生更多事情。这些示例并不意味着完整覆盖 C++20 的协程。由于标准库尚未提供
generator
和task
类,因此我使用 cppcoro 库来编译这些示例。
协程是可以暂停和恢复执行的特殊函数。要定义协程,函数体中必须存在co_return
、 co_await
或co_yield
关键字。 C++20 的协程是无堆栈的;除非被编译器优化,否则它们的状态将分配在堆上。
协程的一个例子是生成器函数,它在每次调用时产生(即生成)一个值:
generator< int > range ( int start, int end) {
while (start < end) {
co_yield start;
start++;
}
// Implicit co_return at the end of this function:
// co_return;
}
for ( int n : range( 0 , 10 )) {
std::cout << n << std::endl;
}
上述range
生成器函数生成从start
到end
(不包括)的值,每个迭代步骤都会生成存储在start
中的当前值。生成器在每次range
调用中维护其状态(在本例中,调用是针对 for 循环中的每次迭代)。 co_yield
接受给定的表达式,产生(即返回)其值,并在该点挂起协程。恢复后,执行会在co_yield
之后继续。
协程的另一个例子是任务,它是在等待任务时执行的异步计算:
task< void > echo (socket s) {
for (;;) {
auto data = co_await s. async_read ();
co_await async_write (s, data);
}
// Implicit co_return at the end of this function:
// co_return;
}
在此示例中,引入了co_await
关键字。如果您正在等待的事物(在本例中为读取或写入)未准备好,此关键字采用一个表达式并暂停执行,否则您将继续执行。 (请注意,在幕后, co_yield
使用co_await
。)
使用任务来延迟评估值:
task< int > calculate_meaning_of_life () {
co_return 42 ;
}
auto meaning_of_life = calculate_meaning_of_life();
// ...
co_await meaning_of_life; // == 42
概念被命名为约束类型的编译时谓词。它们采用以下形式:
template < template-parameter-list >
concept concept-name = constraint-expression;
其中, constraint-expression
计算结果为 constexpr 布尔值。约束应该对语义要求进行建模,例如类型是数字还是可散列。如果给定类型不满足其所绑定的概念(即constraint-expression
返回false
),则会产生编译器错误。由于约束是在编译时评估的,因此它们可以提供更有意义的错误消息和运行时安全性。
// `T` is not limited by any constraints.
template < typename T>
concept always_satisfied = true ;
// Limit `T` to integrals.
template < typename T>
concept integral = std::is_integral_v<T>;
// Limit `T` to both the `integral` constraint and signedness.
template < typename T>
concept signed_integral = integral<T> && std::is_signed_v<T>;
// Limit `T` to both the `integral` constraint and the negation of the `signed_integral` constraint.
template < typename T>
concept unsigned_integral = integral<T> && !signed_integral<T>;
有多种句法形式可以强化概念:
// Forms for function parameters:
// `T` is a constrained type template parameter.
template <my_concept T>
void f (T v);
// `T` is a constrained type template parameter.
template < typename T>
requires my_concept<T>
void f (T v);
// `T` is a constrained type template parameter.
template < typename T>
void f (T v) requires my_concept<T>;
// `v` is a constrained deduced parameter.
void f (my_concept auto v);
// `v` is a constrained non-type template parameter.
template <my_concept auto v>
void g ();
// Forms for auto-deduced variables:
// `foo` is a constrained auto-deduced value.
my_concept auto foo = ...;
// Forms for lambdas:
// `T` is a constrained type template parameter.
auto f = []<my_concept T> (T v) {
// ...
};
// `T` is a constrained type template parameter.
auto f = []< typename T> requires my_concept<T> (T v) {
// ...
};
// `T` is a constrained type template parameter.
auto f = []< typename T> (T v) requires my_concept<T> {
// ...
};
// `v` is a constrained deduced parameter.
auto f = [](my_concept auto v) {
// ...
};
// `v` is a constrained non-type template parameter.
auto g = []<my_concept auto v> () {
// ...
};
requires
关键字用于启动一个requires
子句或一个requires
表达式:
template < typename T>
requires my_concept<T> // `requires` clause.
void f (T);
template < typename T>
concept callable = requires (T f) { f (); }; // `requires` expression.
template < typename T>
requires requires (T x) { x + x; } // `requires` clause and expression on same line.
T add (T a, T b) {
return a + b;
}
请注意, requires
表达式中的参数列表是可选的。 requires
表达式中的每个要求都是以下之一:
template < typename T>
concept callable = requires (T f) { f (); };
typename
关键字后跟类型名称表示,断言给定的类型名称有效。 struct foo {
int foo;
};
struct bar {
using value = int ;
value data;
};
struct baz {
using value = int ;
value data;
};
// Using SFINAE, enable if `T` is a `baz`.
template < typename T, typename = std:: enable_if_t <std::is_same_v<T, baz>>>
struct S {};
template < typename T>
using Ref = T&;
template < typename T>
concept C = requires {
// Requirements on type `T`:
typename T::value; // A) has an inner member named `value`
typename S<T>; // B) must have a valid class template specialization for `S`
typename Ref<T>; // C) must be a valid alias template substitution
};
template <C T>
void g (T a);
g (foo{}); // ERROR: Fails requirement A.
g (bar{}); // ERROR: Fails requirement B.
g (baz{}); // PASS.
template < typename T>
concept C = requires(T x) {
{*x} -> std::convertible_to< typename T::inner>; // the type of the expression `*x` is convertible to `T::inner`
{x + 1 } -> std::same_as< int >; // the expression `x + 1` satisfies `std::same_as<decltype((x + 1))>`
{x * 1 } -> std::convertible_to<T>; // the type of the expression `x * 1` is convertible to `T`
};
requires
关键字表示,指定附加约束(例如本地参数参数上的约束)。 template < typename T>
concept C = requires(T x) {
requires std::same_as< sizeof (x), size_t >;
};
另请参阅:概念库。
C++20 引入了太空船运算符 ( <=>
) 作为编写比较函数的新方法,可减少样板文件并帮助开发人员定义更清晰的比较语义。定义三向比较运算符将自动生成其他比较运算符函数(即==
、 !=
、 <
等)。
引入三种排序:
std::strong_ordering
:强排序区分相等的项目(相同和可互换)。提供less
、 greater
、 equivalent
和equal
排序。比较示例:在列表中搜索特定值、整数值、区分大小写的字符串。std::weak_ordering
:弱排序区分等效项(不相同,但为了比较目的可以互换)。提供less
、 greater
和equivalent
排序。比较示例:不区分大小写的字符串、排序、比较类的部分但非全部可见成员。std::partial_ordering
:部分排序遵循弱排序的相同原则,但包括无法排序的情况。提供less
、 greater
、 equivalent
和unordered
排序。比较示例:浮点值(例如NaN
)。默认的三向比较运算符进行逐成员比较:
struct foo {
int a;
bool b;
char c;
// Compare `a` first, then `b`, then `c` ...
auto operator <=>( const foo&) const = default ;
};
foo f1{ 0 , false , ' a ' }, f2{ 0 , true , ' b ' };
f1 < f2; // == true
f1 == f2; // == false
f1 >= f2; // == false
您还可以定义自己的比较:
struct foo {
int x;
bool b;
char c;
std::strong_ordering operator <=>( const foo& other) const {
return x <=> other. x ;
}
};
foo f1{ 0 , false , ' a ' }, f2{ 0 , true , ' b ' };
f1 < f2; // == false
f1 == f2; // == true
f1 >= f2; // == true
C 风格的指定初始值设定项语法。指定初始值设定项列表中未显式列出的任何成员字段都会默认初始化。
struct A {
int x;
int y;
int z = 123 ;
};
A a {. x = 1 , . z = 2 }; // a.x == 1, a.y == 0, a.z == 2
在 lambda 表达式中使用熟悉的模板语法。
auto f = []< typename T>(std::vector<T> v) {
// ...
};
此功能简化了常见的代码模式,有助于保持范围紧凑,并为常见的生命周期问题提供了优雅的解决方案。
for ( auto v = std::vector{ 1 , 2 , 3 }; auto & e : v) {
std::cout << e;
}
// prints "123"
向优化器提供一个提示:标记语句有很高的执行概率。
switch (n) {
case 1 :
// ...
break ;
[[likely]] case 2 : // n == 2 is considered to be arbitrarily more
// ... // likely than any other value of n
break ;
}
如果可能/不可能属性之一出现在 if 语句的右括号之后,则表明该分支可能/不可能执行其子语句(主体)。
int random = get_random_number_between_x_and_y( 0 , 3 );
if (random > 0 ) [[likely]] {
// body of if statement
// ...
}
它还可以应用于迭代语句的子语句(主体)。
while (unlikely_truthy_condition) [[unlikely]] {
// body of while statement
// ...
}
现在不推荐使用[=]
在 lambda 捕获中隐式捕获this
;更喜欢使用[=, this]
或[=, *this]
显式捕获。
struct int_value {
int n = 0 ;
auto getter_fn () {
// BAD:
// return [=]() { return n; };
// GOOD:
return [=, * this ]() { return n; };
}
};
类现在可以在非类型模板参数中使用。作为模板参数传入的对象具有const T
类型,其中T
是对象的类型,并且具有静态存储持续时间。
struct foo {
foo () = default ;
constexpr foo ( int ) {}
};
template <foo f = {}>
auto get_foo () {
return f;
}
get_foo (); // uses implicit constructor
get_foo<foo{ 123 }>();
虚函数现在可以constexpr
并在编译时求值。 constexpr
虚函数可以覆盖非constexpr
虚函数,反之亦然。
struct X1 {
virtual int f () const = 0;
};
struct X2 : public X1 {
constexpr virtual int f () const { return 2 ; }
};
struct X3 : public X2 {
virtual int f () const { return 3 ; }
};
struct X4 : public X3 {
constexpr virtual int f () const { return 4 ; }
};
constexpr X4 x4;
x4.f(); // == 4
在编译时有条件地选择构造函数是否显式。 explicit(true)
与指定explicit
相同。
struct foo {
// Specify non-integral types (strings, floats, etc.) require explicit construction.
template < typename T>
explicit (!std::is_integral_v<T>) foo(T) {}
};
foo a = 123 ; // OK
foo b = " 123 " ; // ERROR: explicit constructor is not a candidate (explicit specifier evaluates to true)
foo c { " 123 " }; // OK
与constexpr
函数类似,但带有consteval
说明符的函数必须生成常量。这些称为immediate functions
。
consteval int sqr ( int n) {
return n * n;
}
constexpr int r = sqr( 100 ); // OK
int x = 100 ;
int r2 = sqr(x); // ERROR: the value of 'x' is not usable in a constant expression
// OK if `sqr` were a `constexpr` function
将枚举的成员纳入范围以提高可读性。前:
enum class rgba_color_channel { red, green, blue, alpha };
std::string_view to_string (rgba_color_channel channel) {
switch (channel) {
case rgba_color_channel::red: return " red " ;
case rgba_color_channel::green: return " green " ;
case rgba_color_channel::blue: return " blue " ;
case rgba_color_channel::alpha: return " alpha " ;
}
}
后:
enum class rgba_color_channel { red, green, blue, alpha };
std::string_view to_string (rgba_color_channel my_channel) {
switch (my_channel) {
using enum rgba_color_channel;
case red: return " red " ;
case green: return " green " ;
case blue: return " blue " ;
case alpha: return " alpha " ;
}
}
按值捕获参数包:
template < typename ... Args>
auto f (Args&&... args){
// BY VALUE:
return [... args = std::forward<Args>(args)] {
// ...
};
}
通过引用捕获参数包:
template < typename ... Args>
auto f (Args&&... args){
// BY REFERENCE:
return [&... args = std::forward<Args>(args)] {
// ...
};
}
提供用于表示 UTF-8 字符串的标准类型。
char8_t utf8_str[] = u8" u0123 " ;
constinit
说明符要求变量必须在编译时初始化。
const char * g () { return " dynamic initialization " ; }
constexpr const char * f ( bool p) { return p ? " constant initializer " : g (); }
constinit const char * c = f( true ); // OK
constinit const char * d = g( false ); // ERROR: `g` is not constexpr, so `d` cannot be evaluated at compile-time.
如果可变参数宏非空,则通过评估给定参数来帮助支持可变参数宏。
# define F (...) f( 0 __VA_OPT__ (,) __VA_ARGS__)
F(a, b, c) // replaced by f(0, a, b, c)
F() // replaced by f(0)
标准库还提供了概念来构建更复杂的概念。其中一些包括:
核心语言概念:
same_as
- 指定两个类型相同。derived_from
- 指定一个类型是从另一个类型派生的。convertible_to
- 指定一种类型可以隐式转换为另一种类型。common_with
- 指定两种类型共享一个公共类型。integral
- 指定类型是整型。default_constructible
- 指定可以默认构造类型的对象。比较概念:
boolean
- 指定可以在布尔上下文中使用的类型。equality_comparable
- 指定operator==
是等价关系。对象概念:
movable
- 指定某种类型的对象可以移动和交换。copyable
- 指定某种类型的对象可以被复制、移动和交换。semiregular
- 指定某种类型的对象可以被复制、移动、交换和默认构造。regular
- 指定类型是正则类型,即它既是semiregular
类型又是equality_comparable
。可调用概念:
invocable
- 指定可以使用给定的一组参数类型来调用可调用类型。predicate
- 指定可调用类型是布尔谓词。另请参阅:概念。
将printf
的简单性与iostream
的类型安全性结合起来。使用大括号作为占位符,并支持类似于 printf 样式说明符的自定义格式。
std::format ( " {1} {0} " , " world " , " hello " ); // == "hello world"
int x = 123 ;
std::string str = std::format( " x: {} " , x); // str == "x: 123"
// Format to an output iterator:
for ( auto x : { 1 , 2 , 3 }) {
std::format_to (std::ostream_iterator< char >{std::cout, " n " }, " {} " , x);
}
要设置自定义类型的格式:
struct fraction {
int numerator;
int denominator;
};
template <>
struct std ::formatter<fraction>
{
constexpr auto parse (std::format_parse_context& ctx) {
return ctx. begin ();
}
auto format ( const fraction& f, std::format_context& ctx) const {
return std::format_to (ctx. out (), " {0:d}/{1:d} " , f. numerator , f. denominator );
}
};
fraction f{ 1 , 2 };
std::format ( " {} " , f); // == "1/2"
缓冲包装输出流的输出操作,确保同步(即无输出交错)。
std::osyncstream{std::cout} << " The value of x is: " << x << std::endl;
跨度是容器的视图(即非拥有),提供对连续元素组的边界检查访问。由于视图不拥有它们的元素,因此它们的构造和复制成本很低——考虑视图的一种简化方法是它们持有对其数据的引用。与维护指针/迭代器和长度字段相反,跨度将这两者包装在一个对象中。
跨度可以是动态大小的或固定大小的(称为它们的范围)。固定大小的跨度受益于边界检查。
Span 不会传播 const,因此要构造只读 span,请使用std::span<const T>
。
示例:使用动态大小的跨度从各种容器打印整数。
void print_ints (std::span< const int > ints) {
for ( const auto n : ints) {
std::cout << n << std::endl;
}
}
print_ints (std::vector{ 1 , 2 , 3 });
print_ints (std::array< int , 5 >{ 1 , 2 , 3 , 4 , 5 });
int a[ 10 ] = { 0 };
print_ints (a);
// etc.
示例:对于与跨度范围不匹配的容器,静态大小的跨度将无法编译。
void print_three_ints (std::span< const int , 3 > ints) {
for ( const auto n : ints) {
std::cout << n << std::endl;
}
}
print_three_ints (std::vector{ 1 , 2 , 3 }); // ERROR
print_three_ints (std::array< int , 5 >{ 1 , 2 , 3 , 4 , 5 }); // ERROR
int a[ 10 ] = { 0 };
print_three_ints (a); // ERROR
std::array< int , 3 > b = { 1 , 2 , 3 };
print_three_ints (b); // OK
// You can construct a span manually if required:
std::vector c{ 1 , 2 , 3 };
print_three_ints (std::span< const int , 3 >{ c. data (), 3 }); // OK: set pointer and length field.
print_three_ints (std::span< const int , 3 >{ c. cbegin (), c. cend () }); // OK: use iterator pairs.
C++20 提供了一个新的<bit>
标头,它提供了一些位操作,包括 popcount。
std::popcount ( 0u ); // 0
std::popcount ( 1u ); // 1
std::popcount ( 0b1111'0000u ); // 4
<numbers>
头中定义的数学常数包括PI、欧拉数等。
std::numbers:: pi ; // 3.14159...
std::numbers::e; // 2.71828...
谓词函数在编译时上下文中调用时为真。
constexpr bool is_compile_time () {
return std::is_constant_evaluated ();
}
constexpr bool a = is_compile_time(); // true
bool b = is_compile_time(); // false
auto p = std::make_shared< int []>( 5 ); // pointer to `int[5]`
// OR
auto p = std::make_shared< int [ 5 ]>(); // pointer to `int[5]`
字符串(和字符串视图)现在具有starts_with
和ends_with
成员函数来检查字符串是否以给定字符串开头或结尾。
std::string str = " foobar " ;
str.starts_with( " foo " ); // true
str.ends_with( " baz " ); // false
关联容器(例如集合和映射)具有contains
成员函数,可以使用它来代替“查找并检查迭代器末尾”习惯用法。
std::map< int , char > map {{ 1 , ' a ' }, { 2 , ' b ' }};
map.contains( 2 ); // true
map.contains( 123 ); // false
std::set< int > set { 1 , 2 , 3 };
set.contains( 2 ); // true
将对象从一种类型重新解释为另一种类型的更安全的方法。
float f = 123.0 ;
int i = std::bit_cast< int >(f);
安全地计算两个整数的中点(不会溢出)。
std::midpoint ( 1 , 3 ); // == 2
将给定的数组/“类数组”对象转换为std::array
。
std::to_array ( " foo " ); // returns `std::array<char, 4>`
std::to_array< int >({ 1 , 2 , 3 }); // returns `std::array<int, 3>`
int a[] = { 1 , 2 , 3 };
std::to_array (a); // returns `std::array<int, 3>`
将前 N 个参数(其中 N 是std::bind_front
给定函数之后的参数数量)绑定到给定的自由函数、lambda 或成员函数。
const auto f = []( int a, int b, int c) { return a + b + c; };
const auto g = std::bind_front(f, 1 , 1 );
g ( 1 ); // == 3
为各种 STL 容器(例如字符串、列表、向量、映射等)提供std::erase
和/或std::erase_if
要按值擦除,请使用std::erase
,或者要指定何时擦除元素的谓词,请使用std::erase_if
。这两个函数都返回已擦除元素的数量。
std::vector v{ 0 , 1 , 0 , 2 , 0 , 3 };
std::erase (v, 0 ); // v == {1, 2, 3}
std::erase_if (v, []( int n) { return n == 0 ; }); // v == {1, 2, 3}
用于为比较结果命名的辅助函数:
std::is_eq ( 0 <=> 0 ); // == true
std::is_lteq ( 0 <=> 1 ); // == true
std::is_gt ( 0 <=> 1 ); // == false
另请参阅:三向比较。
使用三向比较按字典顺序比较两个范围,并生成最强的适用比较类别类型的结果。
std::vector a{ 0 , 0 , 0 }, b{ 0 , 0 , 0 }, c{ 1 , 1 , 1 };
auto cmp_ab = std::lexicographical_compare_three_way(
a.begin(), a.end(), b.begin(), b.end());
std::is_eq (cmp_ab); // == true
auto cmp_ac = std::lexicographical_compare_three_way(
a.begin(), a.end(), c.begin(), c.end());
std::is_lt (cmp_ac); // == true
另请参阅:三向比较、三向比较助手。
自动模板参数推导非常类似于函数的推导方式,但现在包括类构造函数。
template < typename T = float >
struct MyContainer {
T val;
MyContainer () : val{} {}
MyContainer (T val) : val{val} {}
// ...
};
MyContainer c1 { 1 }; // OK MyContainer<int>
MyContainer c2; // OK MyContainer<float>
遵循auto
的推导规则,同时尊重允许类型[*]的非类型模板参数列表,模板实参可以从其参数的类型推导:
template < auto ... seq>
struct my_integer_sequence {
// Implementation here ...
};
// Explicitly pass type `int` as template argument.
auto seq = std::integer_sequence< int , 0 , 1 , 2 >();
// Type is deduced to be `int`.
auto seq2 = my_integer_sequence< 0 , 1 , 2 >();
* - 例如,您不能使用double
作为模板参数类型,这也使得使用auto
进行无效推导。
折叠表达式通过二元运算符执行模板参数包的折叠。
(... op e)
或(e op ...)
形式的表达式称为一元折叠,其中op
是折叠运算符, e
是未展开的参数包。(e1 op ... op e2)
形式的表达式(其中op
是折叠运算符)称为二元折叠。 e1
或e2
是未扩展的参数包,但不能同时是两者。 template < typename ... Args>
bool logicalAnd (Args... args) {
// Binary folding.
return ( true && ... && args);
}
bool b = true ;
bool & b2 = b;
logicalAnd (b, b2, true ); // == true
template < typename ... Args>
auto sum (Args... args) {
// Unary folding.
return (... + args);
}
sum ( 1.0 , 2 . 0f , 3 ); // == 6.0
与统一初始化语法一起使用时auto
推导的更改。以前, auto x {3};
推导出std::initializer_list<int>
,现在推导出int
。
auto x1 { 1 , 2 , 3 }; // error: not a single element
auto x2 = { 1 , 2 , 3 }; // x2 is std::initializer_list<int>
auto x3 { 3 }; // x3 is int
auto x4 { 3.0 }; // x4 is double
使用constexpr
编译时 lambda。
auto identity = []( int n) constexpr { return n; };
static_assert (identity( 123 ) == 123);
constexpr auto add = []( int x, int y) {
auto L = [=] { return x; };
auto R = [=] { return y; };
return [=] { return L () + R (); };
};
static_assert (add( 1 , 2 )() == 3);
constexpr int addOne ( int n) {
return [n] { return n + 1 ; }();
}
static_assert (addOne( 1 ) == 2);
this
在 lambda 环境中捕获this
以前仅供参考。存在问题的一个示例是使用回调的异步代码,该回调要求对象可用(可能超过其生命周期)。 *this
(C++17) 现在将复制当前对象,而this
(C++11) 继续通过引用捕获。
struct MyObj {
int value { 123 };
auto getValueCopy () {
return [* this ] { return value; };
}
auto getValueRef () {
return [ this ] { return value; };
}
};
MyObj mo;
auto valueCopy = mo.getValueCopy();
auto valueRef = mo.getValueRef();
mo.value = 321 ;
valueCopy (); // 123
valueRef (); // 321
内联说明符可以应用于变量以及函数。声明为内联的变量与声明为内联的函数具有相同的语义。
// Disassembly example using compiler explorer.
struct S { int x; };
inline S x1 = S{ 321 }; // mov esi, dword ptr [x1]
// x1: .long 321
S x2 = S{ 123 }; // mov eax, dword ptr [.L_ZZ4mainE2x2]
// mov dword ptr [rbp - 8], eax
// .L_ZZ4mainE2x2: .long 123
它还可以用于声明和定义静态成员变量,这样就不需要在源文件中对其进行初始化。
struct S {
S () : id{count++} {}
~S () { count--; }
int id;
static inline int count{ 0 }; // declare and initialize count to 0 within the class
};
使用命名空间解析运算符创建嵌套命名空间定义。
namespace A {
namespace B {
namespace C {
int i;
}
}
}
上面的代码可以这样写:
namespace A ::B::C {
int i;
}
解构初始化的提议,允许编写auto [ x, y, z ] = expr;
其中expr
的类型是一个类似元组的对象,其元素将绑定到变量x
、 y
和z
(此构造声明的变量)。类似元组的对象包括std::tuple
、 std::pair
、 std::array
和聚合结构。
using Coordinate = std::pair< int , int >;
Coordinate origin () {
return Coordinate{ 0 , 0 };
}
const auto [ x, y ] = origin();
x; // == 0
y; // == 0
std::unordered_map<std::string, int > mapping {
{ " a " , 1 },
{ " b " , 2 },
{ " c " , 3 }
};
// Destructure by reference.
for ( const auto & [key, value] : mapping) {
// Do something with key and value
}
新版本的if
和switch
语句简化了常见代码模式并帮助用户保持严格的范围。
{
std::lock_guard<std::mutex> lk (mx);
if (v. empty ()) v. push_back (val);
}
// vs.
if (std::lock_guard<std::mutex> lk (mx); v.empty()) {
v. push_back (val);
}
Foo gadget (args);
switch ( auto s = gadget.status()) {
case OK: gadget. zip (); break ;
case Bad: throw BadFoo (s. message ());
}
// vs.
switch (Foo gadget (args); auto s = gadget.status()) {
case OK: gadget. zip (); break ;
case Bad: throw BadFoo (s. message ());
}
编写根据编译时条件实例化的代码。
template < typename T>
constexpr bool isIntegral () {
if constexpr (std::is_integral<T>::value) {
return true ;
} else {
return false ;
}
}
static_assert (isIntegral< int >() == true);
static_assert (isIntegral< char >() == true);
static_assert (isIntegral< double >() == false);
struct S {};
static_assert (isIntegral<S>() == false);
以u8
开头的字符文字是char
类型的字符文字。 UTF-8 字符文字的值等于其 ISO 10646 代码点值。
char x = u8 ' x ' ;
现在可以使用花括号语法初始化枚举。
enum byte : unsigned char {};
byte b { 0 }; // OK
byte c {- 1 }; // ERROR
byte d = byte{ 1 }; // OK
byte e = byte{ 256 }; // ERROR
C++17 引入了三个新属性: [[fallthrough]]
、 [[nodiscard]]
和[[maybe_unused]]
。
[[fallthrough]]
向编译器指示 switch 语句中的失败是预期行为。该属性只能在 switch 语句中使用,并且必须放置在下一个 case/default 标签之前。 switch (n) {
case 1 :
// ...
[[fallthrough]];
case 2 :
// ...
break ;
case 3 :
// ...
[[fallthrough]];
default :
// ...
}
[[nodiscard]]
会发出警告。 [[nodiscard]] bool do_something () {
return is_success; // true for success, false for failure
}
do_something (); // warning: ignoring return value of 'bool do_something()',
// declared with attribute 'nodiscard'
// Only issues a warning when `error_info` is returned by value.
struct [[nodiscard]] error_info {
// ...
};
error_info do_something () {
error_info ei;
// ...
return ei;
}
do_something (); // warning: ignoring returned value of type 'error_info',
// declared with attribute 'nodiscard'
[[maybe_unused]]
向编译器指示变量或参数可能未使用并且是预期的。 void my_callback (std::string msg, [[maybe_unused]] bool error) {
// Don't care if `msg` is an error message, just log it.
log (msg);
}
__has_include (operand)
运算符可在#if
和#elif
表达式中使用,以检查头文件或源文件( operand
)是否可用于包含。
一种用例是使用两个以相同方式工作的库,如果系统上找不到首选库,则使用备份/实验库。
# ifdef __has_include
# if __has_include(<optional>)
# include < optional >
# define have_optional 1
# elif __has_include(<experimental/optional>)
# include < experimental/optional >
# define have_optional 1
# define experimental_optional
# else
# define have_optional 0
# endif
# endif
它还可用于包含各种平台上不同名称或位置下存在的标头,而无需知道程序在哪个平台上运行,OpenGL 标头就是一个很好的例子,它位于 macOS 上的OpenGL
目录和其他平台上的GL
中。平台。
# ifdef __has_include
# if __has_include(<OpenGL/gl.h>)
# include < OpenGL/gl.h >
# include < OpenGL/glu.h >
# elif __has_include(<GL/gl.h>)
# include < GL/gl.h >
# include < GL/glu.h >
# else
# error No suitable OpenGL headers found.
# endif
# endif
类模板参数推导(CTAD) 允许编译器从构造函数参数推导模板参数。
std::vector v{ 1 , 2 , 3 }; // deduces std::vector<int>
std::mutex mtx;
auto lck = std::lock_guard{ mtx }; // deduces to std::lock_guard<std::mutex>
auto p = new std::pair{ 1.0 , 2.0 }; // deduces to std::pair<double, double>*
对于用户定义类型,可以使用推导指南来指导编译器如何推导模板参数(如果适用):
template < typename T>
struct container {
container (T t) {}
template < typename Iter>
container (Iter beg, Iter end);
};
// deduction guide
template < typename Iter>
container (Iter b, Iter e) -> container<typename std::iterator_traits<Iter>::value_type>;
container a{ 7 }; // OK: deduces container<int>
std::vector< double > v{ 1.0 , 2.0 , 3.0 };
auto b = container{ v. begin (), v. end () }; // OK: deduces container<double>
container c{ 5 , 6 }; // ERROR: std::iterator_traits<int>::value_type is not a type
类模板std::variant
表示类型安全的union
。 std::variant
的实例在任何给定时间都保存其替代类型之一的值(它也可能是无值的)。
std::variant< int , double > v{ 12 };
std::get< int >(v); // == 12
std::get< 0 >(v); // == 12
v = 12.0 ;
std::get< double >(v); // == 12.0
std::get< 1 >(v); // == 12.0
类模板std::optional
管理一个可选的包含值,即可能存在也可能不存在的值。可选的一个常见用例是可能失败的函数的返回值。
std::optional<std::string> create ( bool b) {
if (b) {
return " Godzilla " ;
} else {
return {};
}
}
create ( false ).value_or( " empty " ); // == "empty"
create ( true ).value(); // == "Godzilla"
// optional-returning factory functions are usable as conditions of while and if
if ( auto str = create( true )) {
// ...
}
用于任何类型的单个值的类型安全容器。
std::any x { 5 };
x.has_value() // == true
std::any_cast< int >(x) // == 5
std::any_cast< int &>(x) = 10 ;
std::any_cast< int >(x) // == 10
对字符串的非拥有引用。对于在字符串之上提供抽象很有用(例如用于解析)。
// Regular strings.
std::string_view cppstr { " foo " };
// Wide strings.
std::wstring_view wcstr_v { L" baz " };
// Character arrays.
char array[ 3 ] = { ' b ' , ' a ' , ' r ' };
std::string_view array_v (array, std::size(array));
std::string str { " trim me " };
std::string_view v {str};
v.remove_prefix(std::min(v.find_first_not_of( " " ), v.size()));
str; // == " trim me"
v; // == "trim me"
使用参数调用Callable
对象。可调用对象的示例有std::function
或 lambda;可以像常规函数一样调用的对象。
template < typename Callable>
class Proxy {
Callable c_;
public:
Proxy (Callable c) : c_{ std::move (c) } {}
template < typename ... Args>
decltype ( auto ) operator ()(Args&&... args) {
// ...
return std::invoke (c_, std::forward<Args>(args)...);
}
};
const auto add = []( int x, int y) { return x + y; };
Proxy p{ add };
p ( 1 , 2 ); // == 3
使用参数元组调用Callable
对象。
auto add = []( int x, int y) {
return x + y;
};
std::apply (add, std::make_tuple( 1 , 2 )); // == 3
新的std::filesystem
库提供了操作文件系统中的文件、目录和路径的标准方法。
此处,如果有可用空间,则将大文件复制到临时路径:
const auto bigFilePath { " bigFileToCopy " };
if (std::filesystem::exists(bigFilePath)) {
const auto bigFileSize { std::filesystem::file_size (bigFilePath)};
std::filesystem::path tmpPath { " /tmp " };
if ( std::filesystem::space (tmpPath). available > bigFileSize) {
std::filesystem::create_directory (tmpPath. append ( " example " ));
std::filesystem::copy_file (bigFilePath, tmpPath. append ( " newFile " ));
}
}
新的std::byte
类型提供了将数据表示为字节的标准方法。与char
或unsigned char
相比,使用std::byte
的好处是它不是字符类型,也不是算术类型;而唯一可用的运算符重载是按位运算。
std::byte a { 0 };
std::byte b { 0xFF };
int i = std::to_integer< int >(b); // 0xFF
std::byte c = a & b;
int j = std::to_integer< int >(c); // 0
请注意, std::byte
只是一个枚举,并且由于枚举的直接列表初始化,枚举的花括号初始化成为可能。
移动节点和合并容器,无需昂贵的复制、移动或堆分配/解除分配的开销。
将元素从一张地图移动到另一张地图:
std::map< int , string> src {{ 1 , " one " }, { 2 , " two " }, { 3 , " buckle my shoe " }};
std::map< int , string> dst {{ 3 , " three " }};
dst.insert(src.extract(src.find( 1 ))); // Cheap remove and insert of { 1, "one" } from `src` to `dst`.
dst.insert(src.extract( 2 )); // Cheap remove and insert of { 2, "two" } from `src` to `dst`.
// dst == { { 1, "one" }, { 2, "two" }, { 3, "three" } };
插入一整套:
std::set< int > src { 1 , 3 , 5 };
std::set< int > dst { 2 , 4 , 5 };
dst.merge(src);
// src == { 5 }
// dst == { 1, 2, 3, 4, 5 }
插入比容器寿命更长的元素:
auto elementFactory () {
std::set<...> s;
s. emplace (...);
return s. extract (s. begin ());
}
s2.insert(elementFactory());
更改地图元素的键:
std::map< int , string> m {{ 1 , " one " }, { 2 , " two " }, { 3 , " three " }};
auto e = m.extract( 2 );
e.key() = 4 ;
m.insert(std::move(e));
// m == { { 1, "one" }, { 3, "three" }, { 4, "two" } }
许多STL算法,例如copy
、 find
和sort
方法,开始支持并行执行策略: seq
、 par
和par_unseq
它们翻译为“顺序”、“并行”和“并行无序”。
std::vector< int > longVector;
// Find element using parallel execution policy
auto result1 = std::find(std::execution::par, std::begin(longVector), std::end(longVector), 2 );
// Sort elements using sequential execution policy
auto result2 = std::sort(std::execution::seq, std::begin(longVector), std::end(longVector));
对给定序列中的 n 个元素进行采样(不进行替换),其中每个元素被选择的机会均等。
const std::string ALLOWED_CHARS = " abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 " ;
std::string guid;
// Sample 5 characters from ALLOWED_CHARS.
std::sample (ALLOWED_CHARS.begin(), ALLOWED_CHARS.end(), std::back_inserter(guid),
5, std::mt19937{ std::random_device{}() });
std::cout << guid; // e.g. G1fW2
将给定值限制在下限和上限之间。
std::clamp ( 42 , - 1 , 1 ); // == 1
std::clamp (- 42 , - 1 , 1 ); // == -1
std::clamp ( 0 , - 1 , 1 ); // == 0
// `std::clamp` also accepts a custom comparator:
std::clamp ( 0 , - 1 , 1 , std::less<>{}); // == 0
折叠给定范围的元素。概念上类似于std::accumulate
,但std::reduce
将并行执行折叠。由于折叠是并行完成的,如果指定二元运算,则需要结合和交换。给定的二元运算也不应该更改给定范围内的任何元素或使任何迭代器无效。
默认的二元运算是 std::plus,初始值为 0。
const std::array< int , 3 > a{ 1 , 2 , 3 };
std::reduce (std::cbegin(a), std::cend(a)); // == 6
// Using a custom binary op:
std::reduce (std::cbegin(a), std::cend(a), 1, std::multiplies<>{}); // == 6
此外,您还可以指定减速器的转换:
std::transform_reduce (std::cbegin(a), std::cend(a), 0, std::plus<>{}, times_ten); // == 60
const std::array< int , 3 > b{ 1 , 2 , 3 };
const auto product_times_ten = []( const auto a, const auto b) { return a * b * 10 ; };
std::transform_reduce (std::cbegin(a), std::cend(a), std::cbegin(b), 0, std::plus<>{}, product_times_ten); // == 140
支持前缀和(包含和排除扫描)以及转换。
const std::array< int , 3 > a{ 1 , 2 , 3 };
std::inclusive_scan (std::cbegin(a), std::cend(a),
std::ostream_iterator<int>{ std::cout, " " }, std::plus<>{}); // 1 3 6
std::exclusive_scan (std::cbegin(a), std::cend(a),
std::ostream_iterator<int>{ std::cout, " " }, 0 , std::plus<>{}); // 0 1 3
const auto times_ten = []( const auto n) { return n * 10 ; };
std::transform_inclusive_scan (std::cbegin(a), std::cend(a),
std::ostream_iterator<int>{ std::cout, " " }, std::plus<>{}, times_ten); // 10 30 60
std::transform_exclusive_scan (std::cbegin(a), std::cend(a),
std::ostream_iterator<int>{ std::cout, " " }, 0 , std::plus<>{}, times_ten); // 0 10 30
最大公约数 (GCD) 和最小公倍数 (LCM)。
const int p = 9 ;
const int q = 3 ;
std::gcd (p, q); // == 3
std::lcm (p, q); // == 9
返回给定函数结果的否定的实用函数。
const std::ostream_iterator< int > ostream_it{ std::cout, " " };
const auto is_even = []( const auto n) { return n % 2 == 0 ; };
std::vector< int > v{ 0 , 1 , 2 , 3 , 4 };
// Print all even numbers.
std::copy_if (std::cbegin(v), std::cend(v), ostream_it, is_even); // 0 2 4
// Print all odd (not even) numbers.
std::copy_if (std::cbegin(v), std::cend(v), ostream_it, std::not_fn(is_even)); // 1 3
将积分和浮点数转换为字符串,反之亦然。转换是非抛出的、不分配的,并且比 C 标准库中的等效转换更安全。
用户负责分配std::to_chars
所需的足够存储空间,否则该函数将因在其返回值中设置错误代码对象而失败。
这些函数允许您选择传递基数(默认为基数 10)或浮点类型输入的格式说明符。
std::to_chars
返回一个(非常量)char 指针,该指针是函数写入给定缓冲区内的字符串末尾的一位,以及一个错误代码对象。std::from_chars
返回一个 const char 指针,成功时等于传递给函数的结束指针,以及一个错误代码对象。从这些函数返回的两个错误代码对象都等于成功时默认初始化的错误代码对象。
将数字123
转换为std::string
:
const int n = 123 ;
// Can use any container, string, array, etc.
std::string str;
str.resize( 3 ); // hold enough storage for each digit of `n`
const auto [ ptr, ec ] = std::to_chars(str.data(), str.data() + str.size(), n);
if (ec == std::errc{}) { std::cout << str << std::endl; } // 123
else { /* handle failure */ }
从值为"123"
的std::string
转换为整数:
const std::string str{ " 123 " };
int n;
const auto [ ptr, ec ] = std::from_chars(str.data(), str.data() + str.size(), n);
if (ec == std::errc{}) { std::cout << n << std::endl; } // 123
else { /* handle failure */ }
为std::chrono::duration
和std::chrono::time_point
提供abs、round、ceil和floor辅助函数。
using seconds = std::chrono::seconds;
std::chrono::milliseconds d{ 5500 };
std::chrono::abs (d); // == 5s
std::chrono::round<seconds>(d); // == 6s
std::chrono::ceil<seconds>(d); // == 6s
std::chrono::floor<seconds>(d); // == 5s
二进制文字提供了一种表示以 2 为基数的数字的便捷方法。可以用'
分隔数字。
0b110 // == 6
0b1111'1111 // == 255
C++14 现在允许在参数列表中使用auto
类型说明符,从而启用多态 lambda。
auto identity = []( auto x) { return x; };
int three = identity( 3 ); // == 3
std::string foo = identity( " foo " ); // == "foo"
这允许创建使用任意表达式初始化的 lambda 捕获。为捕获的值指定的名称不需要与封闭范围中的任何变量相关,并且会在 lambda 主体内引入新名称。初始化表达式在创建lambda 时进行计算(而不是在调用它时)。
int factory ( int i) { return i * 10 ; }
auto f = [x = factory( 2 )] { return x; }; // returns 20
auto generator = [x = 0 ] () mutable {
// this would not compile without 'mutable' as we are modifying x on each call
return x++;
};
auto a = generator(); // == 0
auto b = generator(); // == 1
auto c = generator(); // == 2
因为现在可以将值移动(或转发)到以前只能通过复制或引用捕获的 lambda 中,所以我们现在可以按值捕获 lambda 中的仅移动类型。请注意,在下面的示例中, =
左侧的task2
捕获列表中的p
是 lambda 主体私有的新变量,并不引用原始p
。
auto p = std::make_unique< int >( 1 );
auto task1 = [=] { *p = 5 ; }; // ERROR: std::unique_ptr cannot be copied
// vs.
auto task2 = [p = std::move(p)] { *p = 5 ; }; // OK: p is move-constructed into the closure object
// the original p is empty after task2 is created
使用此引用捕获可以具有与引用变量不同的名称。
auto x = 1 ;
auto f = [&r = x, x = x * 10 ] {
++r;
return r + x;
};
f (); // sets x to 2 and returns 12
在 C++14 中使用auto
返回类型,编译器将尝试为您推断类型。借助 lambda,您现在可以使用auto
推导其返回类型,这使得返回推导的引用或右值引用成为可能。
// Deduce return type as `int`.
auto f ( int i) {
return i;
}
template < typename T>
auto & f (T& t) {
return t;
}
// Returns a reference to a deduced type.
auto g = []( auto & x) -> auto & { return f (x); };
int y = 123 ;
int & z = g(y); // reference to `y`
decltype(auto)
类型说明符也可以像auto
一样推导类型。但是,它会在保留引用和 cv 限定符的同时推导返回类型,而auto
则不会。
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&&
// Note: Especially useful for generic code!
// 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 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);
另请参见: decltype (C++11)
。
在 C++11 中, constexpr
函数体只能包含非常有限的语法集,包括(但不限于): typedef
、 using
和单个return
语句。在 C++14 中,允许的语法集大大扩展,包括最常见的语法,例如if
语句、多个return
、循环等。
constexpr int factorial ( int n) {
if (n <= 1 ) {
return 1 ;
} else {
return n * factorial (n - 1 );
}
}
factorial ( 5 ); // == 120
C++14 允许变量模板化:
template < class T >
constexpr T pi = T( 3.1415926535897932385 );
template < class T >
constexpr T e = T( 2.7182818284590452353 );
C++14 引入了[[deprecated]]
属性来指示不鼓励使用某个单元(函数、类等),并且可能会产生编译警告。如果提供原因,它将包含在警告中。
[[deprecated]]
void old_method ();
[[deprecated( " Use new_method instead " )]]
void legacy_method ();
标准库类型的新用户定义文字,包括chrono
和basic_string
的新内置文字。这些可以是constexpr
,这意味着它们可以在编译时使用。这些文字的一些用途包括编译时整数解析、二进制文字和虚数文字。
using namespace std ::chrono_literals ;
auto day = 24h;
day.count(); // == 24
std::chrono::duration_cast<std::chrono::minutes>(day).count(); // == 1440
类模板std::integer_sequence
表示编译时整数序列。有一些构建在上面的助手:
std::make_integer_sequence<T, N>
- 创建类型为T
的0, ..., N - 1
序列。std::index_sequence_for<T...>
- 将模板参数包转换为整数序列。将数组转换为元组:
template < typename Array, std:: size_t ... I>
decltype ( auto ) a2t_impl( const Array& a, std::integer_sequence<std:: size_t , I...>) {
return std::make_tuple (a[I]...);
}
template < typename T, std:: size_t N, typename Indices = std::make_index_sequence<N>>
decltype ( auto ) a2t( const std::array<T, N>& a) {
return a2t_impl (a, Indices ());
}
由于以下原因, std::make_unique
是创建std::unique_ptr
实例的推荐方法:
new
运算符。foo
: foo (std::unique_ptr<T>{ new T{}}, function_that_throws(), std::unique_ptr<T>{ new T{}});
编译器可以自由地调用new T{}
,然后调用function_that_throws()
,依此类推...由于我们在T
的第一次构造中已经在堆上分配了数据,因此我们在这里引入了泄漏。使用std::make_unique
,我们获得了异常安全:
foo (std::make_unique<T>(), function_that_throws(), std::make_unique<T>());
有关std::unique_ptr
和std::shared_ptr
的更多信息,请参阅智能指针 (C++11) 部分。
移动对象意味着将其管理的某些资源的所有权转移给另一个对象。
移动语义的第一个好处是性能优化。当一个对象即将结束其生命周期时,无论是因为它是临时的还是通过显式调用std::move
,移动通常是一种更便宜的资源转移方式。例如,移动std::vector
只是将一些指针和内部状态复制到新向量 - 复制将涉及必须复制向量中包含的每个元素,如果旧向量很快就会被复制,那么这是昂贵且不必要的被毁了。
移动还使不可复制类型(例如std::unique_ptr
(智能指针))能够在语言级别保证一次仅管理一个资源实例,同时能够传输实例范围之间。
请参阅以下部分:右值引用、移动语义的特殊成员函数、 std::move
、 std::forward
、 forwarding references
。
C++11 引入了一种新的引用,称为右值引用。对T
右值引用是非模板类型参数(例如int
或用户定义类型),使用语法T&&
创建。右值引用仅绑定到右值。
使用左值和右值进行类型推导:
int x = 0 ; // `x` is an lvalue of type `int`
int & xl = x; // `xl` is an lvalue of type `int&`
int && xr = x; // compiler error -- `x` is an lvalue
int && xr2 = 0 ; // `xr2` is an lvalue of type `int&&` -- binds to the rvalue temporary, `0`
void f ( int & x) {}
void f ( int && x) {}
f (x); // calls f(int&)
f (xl); // calls f(int&)
f ( 3 ); // calls f(int&&)
f (std::move(x)); // calls f(int&&)
f (xr2); // calls f(int&)
f (std::move(xr2)); // calls f(int&& x)
另请参阅: std::move
、 std::forward
、 forwarding references
。
也称为(非官方)通用参考。使用语法T&&
创建转发引用,其中T
是模板类型参数,或使用auto&&
创建。这实现了完美的转发:能够在传递参数的同时保持其值类别(例如,左值保留为左值,临时值作为右值转发)。
转发引用允许引用根据类型绑定到左值或右值。转发引用遵循引用折叠规则:
T& &
变为T&
T& &&
变为T&
T&& &
变为T&
T&& &&
变为T&&
使用左值和右值进行auto
类型推导:
int x = 0 ; // `x` is an lvalue of type `int`
auto && al = x; // `al` is an lvalue of type `int&` -- binds to the lvalue, `x`
auto && ar = 0 ; // `ar` is an lvalue of type `int&&` -- binds to the rvalue temporary, `0`
使用左值和右值进行模板类型参数推导:
// Since C++14 or later:
void f ( auto && t) {
// ...
}
// Since C++11 or later:
template < typename T>
void f (T&& t) {
// ...
}
int x = 0 ;
f ( 0 ); // T is int, deduces as f(int &&) => f(int&&)
f (x); // T is int&, deduces as f(int& &&) => f(int&)
int & y = x;
f (y); // T is int&, deduces as f(int& &&) => f(int&)
int && z = 0 ; // NOTE: `z` is an lvalue with type `int&&`.
f (z); // T is int&, deduces as f(int& &&) => f(int&)
f (std::move(z)); // T is int, deduces as f(int &&) => f(int&&)
另请参见: std::move
、 std::forward
、 rvalue references
。
...
语法创建参数包或扩展参数包。模板参数包是接受零个或多个模板实参(非类型、类型或模板)的模板参数。具有至少一个参数包的模板称为可变参数模板。
template < typename ... T>
struct arity {
constexpr static int value = sizeof ...(T);
};
static_assert (arity<>::value == 0 );
static_assert (arity< char , short , int >::value == 3 );
一个有趣的用途是从参数包创建一个初始化列表,以便迭代可变参数函数参数。
template < typename First, typename ... Args>
auto sum ( const First first, const Args... args) -> decltype(first) {
const auto values = {first, args...};
return std::accumulate (values. begin (), values. end (), First{ 0 });
}
sum ( 1 , 2 , 3 , 4 , 5 ); // 15
sum ( 1 , 2 , 3 ); // 6
sum ( 1.5 , 2.0 , 3.7 ); // 7.2
使用“花括号列表”语法创建的轻量级类似数组的元素容器。例如, { 1, 2, 3 }
创建一个整数序列,其类型为std::initializer_list<int>
。用作将对象向量传递给函数的替代方法。
int sum ( const std::initializer_list< int >& list) {
int total = 0 ;
for ( auto & e : list) {
total += e;
}
return total;
}
auto list = { 1 , 2 , 3 };
sum (list); // == 6
sum ({ 1 , 2 , 3 }); // == 6
sum ({}); // == 0
在编译时评估的断言。
constexpr int x = 0 ;
constexpr int y = 1 ;
static_assert (x == y, " x != y " );
auto
类型变量由编译器根据其初始值设定项的类型推导。
auto a = 3.14 ; // double
auto b = 1 ; // int
auto & c = b; // int&
auto d = { 0 }; // std::initializer_list<int>
auto && e = 1 ; // int&&
auto && f = b; // int&
auto g = new auto ( 123 ); // int*
const auto h = 1 ; // const int
auto i = 1 , j = 2 , k = 3 ; // int, int, int
auto l = 1 , m = true , n = 1.61 ; // error -- `l` deduced to be int, `m` is bool
auto o; // error -- `o` requires initializer
对于可读性非常有用,特别是对于复杂的类型:
std::vector< int > v = ...;
std::vector< int >::const_iterator cit = v.cbegin();
// vs.
auto cit = v.cbegin();
函数还可以使用auto
推导返回类型。在 C++11 中,必须显式指定返回类型,或者使用decltype
指定,如下所示:
template < typename X, typename Y>
auto add (X x, Y y) -> decltype(x + y) {
return x + y;
}
add ( 1 , 2 ); // == 3
add ( 1 , 2.0 ); // == 3.0
add ( 1.5 , 1.5 ); // == 3.0
上面示例中的尾随返回类型是表达式x + y
的声明类型(请参阅有关decltype
的部分)。例如,如果x
是整数且y
是双精度型, decltype(x + y)
是双精度型。因此,上面的函数将根据表达式x + y
产生的类型来推断类型。请注意,尾随返回类型可以访问其参数,并且在适当的情况下可以this
。
lambda
是一个未命名的函数对象,能够捕获作用域中的变量。它的特点是:捕获列表;一组可选的参数,带有可选的尾随返回类型;和一个身体。捕获列表示例:
[]
- 没有捕获任何内容。[=]
- 按值捕获范围内的局部对象(局部变量、参数)。[&]
- 通过引用捕获作用域中的局部对象(局部变量、参数)。[this]
- 通过引用捕获this
。[a, &b]
- 通过值捕获对象a
,通过引用捕获b
。 int x = 1 ;
auto getX = [=] { return x; };
getX (); // == 1
auto addX = [=]( int y) { return x + y; };
addX ( 1 ); // == 2
auto getXRef = [&]() -> int & { return x; };
getXRef (); // int& to `x`
默认情况下,无法在 lambda 内部修改值捕获,因为编译器生成的方法被标记为const
。 mutable
关键字允许修改捕获的变量。关键字放置在参数列表之后(即使参数列表为空,也必须存在)。
int x = 1 ;
auto f1 = [&x] { x = 2 ; }; // OK: x is a reference and modifies the original
auto f2 = [x] { x = 2 ; }; // ERROR: the lambda can only perform const-operations on the captured value
// vs.
auto f3 = [x]() mutable { x = 2 ; }; // OK: the lambda can perform any operations on the captured value
decltype
是一个运算符,它返回传递给它的表达式的声明类型。如果 cv 限定符和引用是表达式的一部分,则它们会被保留。 decltype
的示例:
int a = 1 ; // `a` is declared as type `int`
decltype (a) b = a; // `decltype(a)` is `int`
const int & c = a; // `c` is declared as type `const int&`
decltype (c) d = a; // `decltype(c)` is `const int&`
decltype ( 123 ) e = 123; // `decltype(123)` is `int`
int && f = 1 ; // `f` is declared as type `int&&`
decltype (f) g = 1; // `decltype(f) is `int&&`
decltype ((a)) h = g; // `decltype((a))` is int&
template < typename X, typename Y>
auto add (X x, Y y) -> decltype(x + y) {
return x + y;
}
add ( 1 , 2.0 ); // `decltype(x + y)` => `decltype(3.0)` => `double`
另请参见: decltype(auto) (C++14)
。
然而,在语义上与使用typedef
类似,使用using
类型别名更易于阅读并且与模板兼容。
template < typename T>
using Vec = std::vector<T>;
Vec< int > v; // std::vector<int>
using String = std::string;
String s { " foo " };
C++11 引入了一种新的空指针类型,旨在取代 C 的NULL
宏。 nullptr
本身是std::nullptr_t
类型,可以隐式转换为指针类型,并且与NULL
不同,不能转换为除bool
之外的整型类型。
void foo ( int );
void foo ( char *);
foo ( NULL ); // error -- ambiguous
foo ( nullptr ); // calls foo(char*)
类型安全枚举解决了 C 风格枚举的各种问题,包括:隐式转换、无法指定基础类型、范围污染。
// Specifying underlying type as `unsigned int`
enum class Color : unsigned int { Red = 0xff0000 , Green = 0xff00 , Blue = 0xff };
// `Red`/`Green` in `Alert` don't conflict with `Color`
enum class Alert : bool { Red, Green };
Color c = Color::Red;
属性提供了__attribute__(...)
、 __declspec
等的通用语法。
// `noreturn` attribute indicates `f` doesn't return.
[[ noreturn ]] void f () {
throw " error " ;
}
常量表达式是编译器在编译时可能计算的表达式。常量表达式中只能进行非复杂的计算(这些规则在以后的版本中逐渐放宽)。使用constexpr
说明符来指示变量、函数等是常量表达式。
constexpr int square ( int x) {
return x * x;
}
int square2 ( int x) {
return x * x;
}
int a = square( 2 ); // mov DWORD PTR [rbp-4], 4
int b = square2( 2 ); // mov edi, 2
// call square2(int)
// mov DWORD PTR [rbp-8], eax
在上一个片段中,请注意,调用square
时的计算是在编译时执行的,然后将结果嵌入代码生成中,而square2
在运行时被调用。
constexpr
值是编译器可以在编译时评估但不能保证的值:
const int x = 123 ;
constexpr const int & y = x; // error -- constexpr variable `y` must be initialized by a constant expression
与类的恒定表达:
struct Complex {
constexpr Complex ( double r, double i) : re{r}, im{i} { }
constexpr double real () { return re; }
constexpr double imag () { return im; }
private:
double re;
double im;
};
constexpr Complex I ( 0 , 1 );
构造函数现在可以使用初始化器列表在同一类中调用其他构造函数。
struct Foo {
int foo;
Foo ( int foo) : foo{foo} {}
Foo () : Foo( 0 ) {}
};
Foo foo;
foo.foo; // == 0
用户定义的文字允许您扩展语言并添加自己的语法。要创建一个字面的,请定义T operator "" X(...) { ... }
函数,该函数返回type T
,带有名称X
。请注意,此函数的名称定义了文字的名称。任何不以下划线开头的字面名称都保留,不会被调用。根据所调用的字体类型,可以接受用户定义的文字函数应接受哪些参数的规则。
将摄氏摄氏转换为华氏:
// `unsigned long long` parameter required for integer literal.
long long operator " " _celsius( unsigned long long tempCelsius) {
return std::llround (tempCelsius * 1.8 + 32 );
}
24_celsius; // == 75
字符串到整数转换:
// `const char*` and `std::size_t` required as parameters.
int operator " " _int( const char * str, std:: size_t ) {
return std::stoi (str);
}
" 123 " _int; // == 123, with type `int`
指定虚拟函数覆盖另一个虚拟函数。如果虚拟函数没有覆盖父的虚拟函数,则会引发编译器错误。
struct A {
virtual void foo ();
void bar ();
};
struct B : A {
void foo () override ; // correct -- B::foo overrides A::foo
void bar () override ; // error -- A::bar is not virtual
void baz () override ; // error -- B::baz does not override A::baz
};
指定在派生的类中不能覆盖虚拟函数,也不能从派生的类中继承。
struct A {
virtual void foo ();
};
struct B : A {
virtual void foo () final ;
};
struct C : B {
virtual void foo (); // error -- declaration of 'foo' overrides a 'final' function
};
班级不能继承。
struct A final {};
struct B : A {}; // error -- base 'A' is marked 'final'
一种更优雅,有效的方法来提供函数的默认实现,例如构造函数。
struct A {
A () = default ;
A ( int x) : x{x} {}
int x { 1 };
};
A a; // a.x == 1
A a2 { 123 }; // a.x == 123
继承:
struct B {
B () : x{ 1 } {}
int x;
};
struct C : B {
// Calls B::B
C () = default ;
};
C c; // c.x == 1
一种更优雅,更有效的方法来提供删除的函数实现。对于防止对象上的副本有用。
class A {
int x;
public:
A ( int x) : x{x} {};
A ( const A&) = delete ;
A& operator =( const A&) = delete ;
};
A x { 123 };
A y = x; // error -- call to deleted copy constructor
y = x; // error -- operator= deleted
在容器元素上迭代的句法糖。
std::array< int , 5 > a { 1 , 2 , 3 , 4 , 5 };
for ( int & x : a) x *= 2 ;
// a == { 2, 4, 6, 8, 10 }
请注意使用int
而不是int&
差异:
std::array< int , 5 > a { 1 , 2 , 3 , 4 , 5 };
for ( int x : a) x *= 2 ;
// a == { 1, 2, 3, 4, 5 }
复制构造函数和副本分配运算符在制作副本时被调用,并且随着C ++ 11的Move Semantics的介绍,现在有一个移动构造函数和移动分配运算符进行移动。
struct A {
std::string s;
A () : s{ " test " } {}
A ( const A& o) : s{o. s } {}
A (A&& o) : s{ std::move (o. s )} {}
A& operator =(A&& o) {
s = std::move (o. s );
return * this ;
}
};
A f (A a) {
return a;
}
A a1 = f(A{}); // move-constructed from rvalue temporary
A a2 = std::move(a1); // move-constructed using std::move
A a3 = A{};
a2 = std::move(a3); // move-assignment using std::move
a1 = f(A{}); // move-assignment from rvalue temporary
转换构造函数将将布置列表语法的值转换为构造函数参数。
struct A {
A ( int ) {}
A ( int , int ) {}
A ( int , int , int ) {}
};
A a { 0 , 0 }; // calls A::A(int, int)
A b ( 0 , 0 ); // calls A::A(int, int)
A c = { 0 , 0 }; // calls A::A(int, int)
A d { 0 , 0 , 0 }; // calls A::A(int, int, int)
请注意,支撑列表语法不允许缩小:
struct A {
A ( int ) {}
};
A a ( 1.1 ); // OK
A b { 1.1 }; // Error narrowing conversion from double to int
请注意,如果构造函数接受std::initializer_list
,则将被调用:
struct A {
A ( int ) {}
A ( int , int ) {}
A ( int , int , int ) {}
A (std::initializer_list< int >) {}
};
A a { 0 , 0 }; // calls A::A(std::initializer_list<int>)
A b ( 0 , 0 ); // calls A::A(int, int)
A c = { 0 , 0 }; // calls A::A(std::initializer_list<int>)
A d { 0 , 0 , 0 }; // calls A::A(std::initializer_list<int>)
现在,可以使用explicit
说明符明确将转换功能显式。
struct A {
operator bool () const { return true ; }
};
struct B {
explicit operator bool () const { return true ; }
};
A a;
if (a); // OK calls A::operator bool()
bool ba = a; // OK copy-initialization selects A::operator bool()
B b;
if (b); // OK calls B::operator bool()
bool bb = b; // error copy-initialization does not consider B::operator bool()
内联命名空间的所有成员都被视为它们是其父命名空间的一部分,允许对功能进行专业化并简化版本控制的过程。如果A包含B,则这是一个瞬态属性,而B又包含C,并且B和C均为内联命名空间,C的成员可以像在A上一样使用。
namespace Program {
namespace Version1 {
int getVersion () { return 1 ; }
bool isFirstVersion () { return true ; }
}
inline namespace Version2 {
int getVersion () { return 2 ; }
}
}
int version { Program::getVersion ()}; // Uses getVersion() from Version2
int oldVersion { Program::Version1::getVersion ()}; // Uses getVersion() from Version1
bool firstVersion { Program::isFirstVersion ()}; // Does not compile when Version2 is added
允许在声明的地方初始化非静态数据成员,从而有可能清理默认初始化的构造函数。
// Default initialization prior to C++11
class Human {
Human () : age{ 0 } {}
private:
unsigned age;
};
// Default initialization on C++11
class Human {
private:
unsigned age { 0 };
};
当使用一系列直角括号作为操作员或TypeDef的闭合语句时,C ++ 11现在就可以推断出,而无需添加空格。
typedef std::map< int , std::map < int , std::map < int , int > > > cpp98LongTypedef;
typedef std::map< int , std::map < int , std::map < int , int >>> cpp11LongTypedef;
现在可以根据*this
是lvalue或rvalue参考,可以将成员函数符合条件。
struct Bar {
// ...
};
struct Foo {
Bar& getBar () & { return bar; }
const Bar& getBar () const & { return bar; }
Bar&& getBar() && { return std::move (bar); }
const Bar&& getBar() const && { return std::move (bar); }
private:
Bar bar;
};
Foo foo{};
Bar bar = foo.getBar(); // calls `Bar& getBar() &`
const Foo foo2{};
Bar bar2 = foo2.getBar(); // calls `Bar& Foo::getBar() const&`
Foo{}.getBar(); // calls `Bar&& Foo::getBar() &&`
std::move (foo).getBar(); // calls `Bar&& Foo::getBar() &&`
std::move (foo2).getBar(); // calls `const Bar&& Foo::getBar() const&`
C ++ 11允许函数,lambdas是指定其返回类型的替代语法。
int f () {
return 123 ;
}
// vs.
auto f () -> int {
return 123 ;
}
auto g = []() -> int {
return 123 ;
};
当无法解决某些返回类型时,此功能特别有用:
// NOTE: This does not compile!
template < typename T, typename U>
decltype (a + b) add(T a, U b) {
return a + b;
}
// Trailing return types allows this:
template < typename T, typename U>
auto add (T a, U b) -> decltype(a + b) {
return a + b;
}
在C ++ 14中,可以使用decltype(auto) (C++14)
。
noexcept
Specifier指定功能是否可以引发异常。这是throw()
的改进版本。
void func1 () noexcept ; // does not throw
void func2 () noexcept ( true ); // does not throw
void func3 () throw(); // does not throw
void func4 () noexcept ( false ); // may throw
允许非投入功能调用潜在的启动功能。每当抛出异常并且搜索处理程序的搜索都会遇到非直接函数的最外层块,则调用函数std :: terminate。
extern void f (); // potentially-throwing
void g () noexcept {
f (); // valid, even if f throws
throw 42 ; // valid, effectively a call to std::terminate
}
提供表示UTF-8字符串的标准类型。
char32_t utf8_str[] = U" u0123 " ;
char16_t utf8_str[] = u" u0123 " ;
C ++ 11引入了一种将字符串文字称为“原始字符串文字”的新方法。从逃生序列发出的字符(TABS,行馈电,单个后斜线等)可以在保留格式时输入原始。例如,这很有用,可以撰写文学文本,其中可能包含许多引号或特殊格式。这可以使您的字符串文字更易于阅读和维护。
使用以下语法声明原始字符串文字:
R"delimiter(raw_characters)delimiter"
在哪里:
delimiter
是由括号,后斜切和空格除外的任何源字符制成的字符的可选序列。raw_characters
是任何RAW字符序列;不得包含结尾序列")delimiter"
。例子:
// msg1 and msg2 are equivalent.
const char * msg1 = " n Hello, nt world! n " ;
const char * msg2 = R"(
Hello,
world!
)" ;
std::move
指示传递给其对象的资源可能会转移。使用已移动的对象应谨慎使用,因为它们可以在未指定的状态下保留(请参阅:我可以用移动的从对象来做什么?)。
std::move
的定义(执行移动不过是投射到RVALUE参考):
template < typename T>
typename remove_reference<T>::type&& move(T&& arg) {
return static_cast < typename remove_reference<T>::type&&>(arg);
}
Transfring std::unique_ptr
S:
std::unique_ptr< int > p1 { new int { 0 }}; // in practice, use std::make_unique
std::unique_ptr< int > p2 = p1; // error -- cannot copy unique pointers
std::unique_ptr< int > p3 = std::move(p1); // move `p1` into `p3`
// now unsafe to dereference object held by `p1`
在维护其价值类别和CV-Qualifiers的同时返回传递的论点。对通用代码和工厂有用。与forwarding references
结合使用。
std::forward
的定义:
template < typename T>
T&& forward( typename remove_reference<T>::type& arg) {
return static_cast <T&&>(arg);
}
功能wrapper
的示例,该函数包装器只是将其他A
转发到新A
对象的副本或移动构造函数:
struct A {
A () = default ;
A ( const A& o) { std::cout << " copied " << std::endl; }
A (A&& o) { std::cout << " moved " << std::endl; }
};
template < typename T>
A wrapper (T&& arg) {
return A{std::forward<T>(arg)};
}
wrapper (A{}); // moved
A a;
wrapper (a); // copied
wrapper (std::move(a)); // moved
另请参阅: forwarding references
, rvalue references
。
std::thread
库提供了一种控制线程的标准方法,例如产卵和杀死它们。在下面的示例中,产生多个线程以进行不同的计算,然后程序等待所有线程完成。
void foo ( bool clause) { /* do something... */ }
std::vector<std::thread> threadsVector;
threadsVector.emplace_back([]() {
// Lambda function that will be invoked
});
threadsVector.emplace_back(foo, true ); // thread will run foo(true)
for ( auto & thread : threadsVector) {
thread. join (); // Wait for threads to finish
}
将数字参数转换为std::string
。
std::to_string ( 1.2 ); // == "1.2"
std::to_string ( 123 ); // == "123"
类型特征定义了基于编译时模板的接口来查询或修改类型的属性。
static_assert (std::is_integral< int >::value);
static_assert (std::is_same< int , int >::value);
static_assert (std::is_same<std::conditional< true , int , double >::type, int >::value);
C ++ 11介绍了新的智能指针: std::unique_ptr
, std::shared_ptr
, std::weak_ptr
。 std::auto_ptr
现在被弃用,然后最终在C ++ 17中删除。
std::unique_ptr
是一种不可仿制的可移动指针,它可以管理自己的堆分配的内存。注意:更喜欢使用std::make_X
助手功能而不是使用构造函数。请参阅std :: make_unique和std :: make_shared的部分。
std::unique_ptr<Foo> p1 { new Foo{} }; // `p1` owns `Foo`
if (p1) {
p1-> bar ();
}
{
std::unique_ptr<Foo> p2 { std::move (p1)}; // Now `p2` owns `Foo`
f (*p2);
p1 = std::move (p2); // Ownership returns to `p1` -- `p2` gets destroyed
}
if (p1) {
p1-> bar ();
}
// `Foo` instance is destroyed when `p1` goes out of scope
std::shared_ptr
是一个明智的指针,它管理在多个所有者之间共享的资源。共享指针拥有一个控制块,该控制块具有一些组件,例如托管对象和参考计数器。所有控制块访问均为线程安全,但是,操纵托管对象本身不是线程安全。
void foo (std::shared_ptr<T> t) {
// Do something with `t`...
}
void bar (std::shared_ptr<T> t) {
// Do something with `t`...
}
void baz (std::shared_ptr<T> t) {
// Do something with `t`...
}
std::shared_ptr<T> p1 { new T{}};
// Perhaps these take place in another threads?
foo (p1);
bar (p1);
baz (p1);
Chrono库包含一组实用程序功能和类型,这些功能和类型涉及持续时间,时钟和时间点。该库的一种用例是基准测试代码:
std::chrono::time_point<std::chrono::steady_clock> start, end;
start = std::chrono::steady_clock::now();
// Some computations...
end = std::chrono::steady_clock::now();
std::chrono::duration< double > elapsed_seconds = end - start;
double t = elapsed_seconds.count(); // t number of seconds, represented as a `double`
元组是异质值的固定尺寸集合。通过使用std::tie
或使用std::get
键入包装,访问std::tuple
的元素。
// `playerProfile` has type `std::tuple<int, const char*, const char*>`.
auto playerProfile = std::make_tuple( 51 , " Frans Nielsen " , " NYI " );
std::get< 0 >(playerProfile); // 51
std::get< 1 >(playerProfile); // "Frans Nielsen"
std::get< 2 >(playerProfile); // "NYI"
创建元组的LVALUE参考。可用于解开std::pair
和std::tuple
对象。使用std::ignore
作为忽略值的占位符。在C ++ 17中,应改用结构化绑定。
// With tuples...
std::string playerName;
std::tie (std::ignore, playerName, std::ignore) = std::make_tuple( 91 , " John Tavares " , " NYI " );
// With pairs...
std::string yes, no;
std::tie (yes, no) = std::make_pair( " yes " , " no " );
std::array
是建在C风格阵列顶部的容器。支持常见的容器操作,例如排序。
std::array< int , 3 > a = { 2 , 1 , 3 };
std::sort (a.begin(), a.end()); // a == { 1, 2, 3 }
for ( int & x : a) x *= 2 ; // a == { 2, 4, 6 }
这些容器保持平均恒定时间复杂性,以供搜索,插入和删除操作。为了达到恒定的时间复杂性,通过将载入元素放入存储桶中为速度牺牲秩序。有四个无序的容器:
unordered_set
unordered_multiset
unordered_map
unordered_multimap
std::make_shared
是创建std::shared_ptr
s实例的推荐方法,这是由于以下原因:
new
操作员。foo
: foo (std::shared_ptr<T>{ new T{}}, function_that_throws(), std::shared_ptr<T>{ new T{}});
编译器可以自由调用新的T
new T{}
,然后function_that_throws()
等等。使用std::make_shared
,我们得到了例外安全:
foo (std::make_shared<T>(), function_that_throws(), std::make_shared<T>());
std::shared_ptr{ new T{} }
时,我们必须为T
分配内存,然后在共享指针中为指针内的控制块分配内存。有关std::unique_ptr
和std::shared_ptr
的更多信息,请参见有关智能指针的部分。
std::ref(val)
用于创建具有VAL引用的类型std::reference_wrapper
的对象。在通常使用&
不编译或&
由于类型扣除而被删除的情况下使用。 std::cref
相似,但创建的参考包装器将const引用到Val。
// create a container to store reference of objects.
auto val = 99 ;
auto _ref = std::ref(val);
_ref++;
auto _cref = std::cref(val);
// _cref++; does not compile
std::vector<std::reference_wrapper< int >>vec; // vector<int&>vec does not compile
vec.push_back(_ref); // vec.push_back(&i) does not compile
cout << val << endl; // prints 100
cout << vec[ 0 ] << endl; // prints 100
cout << _cref; // prints 100
C ++ 11引入了C ++的存储模型,这意味着库支持线程和原子操作。其中一些操作包括(但不限于)原子载荷/商店,比较和swap,原子标志,承诺,期货,锁和状况变量。
请参阅以下部分:std ::线程
std::async
在异步或懒惰评估中运行给定函数,然后返回一个std::future
,该:: Future持有该功能调用的结果。
第一个参数是可以是:
std::launch::async | std::launch::deferred
是执行异步执行还是懒惰评估的实现。std::launch::async
在新线程上运行可呼叫对象。std::launch::deferred
当前线程上执行懒惰评估。 int foo () {
/* Do something here, then return the result. */
return 1000 ;
}
auto handle = std::async(std::launch::async, foo); // create an async task
auto result = handle.get(); // wait for the result
std::begin
and std::end
free功能添加以返回集装箱的启动和结束迭代器。这些功能还可以与未begin
和end
成员功能的原始数组一起使用。
template < typename T>
int CountTwos ( const T& container) {
return std::count_if ( std::begin (container), std::end (container), []( int item) {
return item == 2 ;
});
}
std::vector< int > vec = { 2 , 2 , 43 , 435 , 4543 , 534 };
int arr[ 8 ] = { 2 , 43 , 45 , 435 , 32 , 32 , 32 , 32 };
auto a = CountTwos(vec); // 2
auto b = CountTwos(arr); // 1
安东尼·卡兰德拉(Anthony Calandra)
请参阅:https://github.com/anthonycalandra/modern-cpp-features/graphs/contributors
麻省理工学院