Coroutines(协程)
协程是一种特殊的函数,它的执行可以被暂停或恢复。要定义协程,关键字co_return ,co_await,或co_yield 必须出现在函数体中。c++ 20 的协程是无栈的;除非编译器进行了优化,否则它们的状态是在堆上分配的。
协程的一个例子是 generator 函数,它在每次调用时生成一个值:
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,它是一个在等待任务时执行的异步计算:
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
复制代码
注意:虽然这些示例说明了如何在基本级别上使用协程,但在编译代码时还有更多内容。这些例子并不意味着完全覆盖 c++ 20 的协程。由于标准库还没有提供generator和task类,所以我使用 cppcoro 库来编译这些示例。
Concepts(概念)
概念被命名为约束类型的编译时谓词。它们的形式如下:
template < template-parameter-list >concept concept-name = constraint-expression;
复制代码
其中constraint-expression计算为 constexpr 布尔值。约束应该对语义需求进行建模,例如类型是数字类型还是可哈希类型。如果给定的类型不满足它所绑定的概念(例如:“约束表达式”返回“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 关键字可以用来启动一个 require 子句或一个 require 表达式:
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 表达式中的参数列表是可选的。require 表达式中的每个需求都是下列要求之一:
template <typename T>concept callable = requires (T f) { f(); };
复制代码
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} -> 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} -> T; // the type of the expression `x * 1` is convertible to `T`};
复制代码
template <typename T>concept C = requires(T x) { requires std::same_as<sizeof(x), size_t>;};
复制代码
Designated initializers(指定初始化式)
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
复制代码
Template syntax for lambdas(lambda 的模板语法)
在 lambda 表达式中使用熟悉的模板语法。
auto f = []<typename T>(std::vector<T> v) { // ...};
复制代码
Range-based for loop with initializer(带初始化器的基于范围的 for 循环)
该特性简化了常见的代码模式,有助于保持范围紧凑,并为常见的生存期问题提供了优雅的解决方案。
for (std::vector v{1, 2, 3}; auto& e : v) { std::cout << e;}// prints "123"
复制代码
likely and unlikely attributes(可能和不可能的属性)
向优化器提供提示,说明已标记语句执行的概率很高。
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 // ...}
复制代码
Deprecate implicit capture of this(不建议隐式捕获)
在 lamdba 捕获中使用[=]隐式捕获this现在已弃用;更喜欢使用[=,this]或[=,*this]显式捕获。
struct int_value { int n = 0; auto getter_fn() { // BAD: // return [=]() { return n; };
// GOOD: return [=, *this]() { return n; }; }};
复制代码
Class types in non-type template parameters(非类型模板形参中的类型)
类现在可以在非类型模板参数中使用。作为模板参数传入的对象的类型为const T,其中T是对象的类型,并且具有静态存储时间。
struct foo { foo() = default; constexpr foo(int) {}};
template <foo f>auto get_foo() { return f;}
get_foo(); // uses implicit constructorget_foo<foo{123}>();
复制代码
constexpr virtual functions(constexpr 虚函数)
虚函数现在可以是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(bool)(是否显式)
在编译时有条件地选择构造函数是否显式。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; // OKfoo b = "123"; // ERROR: explicit constructor is not a candidate (explicit specifier evaluates to true)foo c {"123"}; // OK
复制代码
Immediate functions
类似于constexpr函数,但是带有consteval说明符的函数必须产生一个常量。这些被称为“直接函数”。
consteval int sqr(int n) { return n * n;}
constexpr int r = sqr(100); // OKint 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
复制代码
using enum
将枚举成员引入作用域以提高可读性。之前:
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"; }}
复制代码
Lambda capture of parameter pack(参数包的 Lambda 捕获)
捕获参数按值包:
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)] { // ... };}
复制代码
char8_t
提供表示 UTF-8 字符串的标准类型。
char8_t utf8_str[] = u8"\u0123";
复制代码
c++ 20 库功能
Concepts library
标准库还提供了用于构建更复杂概念的概念。其中包括:
核心语言概念:
same_as - 指定两种相同的类型。
derived_from - 指定一个类型派生自另一个类型。
convertible_to - 指定一个类型可隐式转换为另一个类型。
common_with - 指定两个类型共享一个公共类型。
integral - 指定类型为整型。
default_constructible - 指定可以默认构造类型的对象。
Comparison concepts:
Object concepts:
movable - 指定可移动和交换某一类型的对象。
copyable - 指定可复制、移动和交换某一类型的对象。
semiregular - 指定某个类型的对象可以被复制、移动、交换和默认构造。
regular - 指定类型为 regular,即既为semiregular又为equality_comparable。
Callable concepts:
Synchronized buffered outputstream
缓冲包装输出流的输出操作,以确保同步(即输出没有交错)。
std::osyncstream{std::cout} << "The value of x is:" << x << std::endl;
复制代码
std::span
span 是容器的一个视图(即非所有者视图),它提供了对连续元素组的边界检查访问。由于视图不拥有它们自己的元素,它们的构造和复制成本很低——考虑视图的一种简单方法是它们持有对其数据的引用。跨度可以是动态大小的,也可以是固定大小的。
void f(std::span<int> ints) { std::for_each(ints.begin(), ints.end(), [](auto i) { // ... });}
std::vector<int> v = {1, 2, 3};f(v);std::array<int, 3> a = {1, 2, 3};f(a);// etc.
复制代码
示例:与维护指针和长度字段不同,span 将这两个字段打包在一个容器中。
constexpr size_t LENGTH_ELEMENTS = 3;int* arr = new int[LENGTH_ELEMENTS]; // arr = {0, 0, 0}
// Fixed-sized span which provides a view of `arr`.std::span<int, LENGTH_ELEMENTS> span = arr;span[1] = 1; // arr = {0, 1, 0}
// Dynamic-sized span which provides a view of `arr`.std::span<int> d_span = arr;span[0] = 1; // arr = {1, 1, 0}constexpr size_t LENGTH_ELEMENTS = 3;int* arr = new int[LENGTH_ELEMENTS];
std::span<int, LENGTH_ELEMENTS> span = arr; // OKstd::span<double, LENGTH_ELEMENTS> span2 = arr; // ERRORstd::span<int, 1> span3 = arr; // ERROR
复制代码
Bit operations
c++ 20 提供了一个新的<bit>头,它提供了一些位操作,包括 popcount。
std::popcount(0u); // 0std::popcount(1u); // 1std::popcount(0b1111`0000u); // 4
复制代码
Math constants
在<numbers> header 中定义的数学常量,包括 PI、欧拉数等。
std::numbers::pi; // 3.14159...std::numbers::e; // 2.71828...
复制代码
std::is_constant_evaluated
谓词函数,当它在编译时上下文中被调用时为真
constexpr bool is_compile_time() { return std::is_constant_evaluated();}
constexpr bool a = is_compile_time(); // truebool b = is_compile_time(); // false
复制代码
std::make_shared supports arrays
auto p = std::make_shared<int[]>(5); // pointer to `int[5]`// ORauto p = std::make_shared<int[5]>(); // pointer to `int[5]`
复制代码
starts_with and ends_with on strings
字符串(和字符串视图)现在有starts_with和ends_with成员函数来检查一个字符串是否以给定的字符串开始或结束。
std::string str = "foobar";str.starts_with("foo"); // truestr.ends_with("baz"); // false
复制代码
Check if associative container has element
像集合和映射这样的关联容器有一个“contains”成员函数,它可以用来代替“查找和检查迭代器的结束”习惯用法。
std::map<int, char> map {{1, `a`}, {2, `b`}};map.contains(2); // truemap.contains(123); // false
std::set<int> set {1, 2, 3};set.contains(2); // true
复制代码
std::bit_cast
将对象从一种类型重新解释为另一种类型的更安全的方法。
float f = 123.0;int i = std::bit_cast<int>(f);
复制代码
std::midpoint
安全地计算两个整数的中点(不溢出)。
std::midpoint(1, 3); // == 2
复制代码
std::to_array
将给定的数组/"array-like"对象转换为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>`
复制代码
C++后端开发 、Linux 服务器开发/架构师面试题、学习资料、教学视频和学习路线图(资料包括 C/C++,Linux,golang 技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg 等),免费分享有需要的可以自行添加学习交流群 960994558
评论