C++ 参考手册
- C++11
- C++14
- C++17
- C++20
- C++ 编译器支持情况表
- 独立与宿主实现
- C++ 语言
- 变量模板(C++14 起)
- 整数字面量
- 聚合初始化
- 比较运算符
- 默认比较(C++20 起)
- 转义序列
- for 循环
- while 循环
- 用户定义转换
- SFINAE
- 主函数
- ASCII 码表
- 标识符
- 类型
- 内存模型
- 对象
- 基本概念
- 表达式
- 声明
- 初始化
- 函数
- 语句
- 类
- 运算符重载
- 模板
- 异常
- 事务性内存
- 占位符类型说明符 (C++11 起)
- decltype 说明符
- 函数声明
- final 说明符 (C++11 起)
- override 说明符(C++11 起)
- 引用声明
- 移动构造函数
- 移动赋值运算符
- 枚举声明
- constexpr 说明符(C++11 起)
- 列表初始化 (C++11 起)
- 构造函数与成员初始化器列表
- using 声明
- nullptr,指针字面量
- 基础类型
- 类型别名,别名模版 (C++11 起)
- 形参包
- 联合体声明
- 字符串字面量
- 用户定义字面量 (C++11 起)
- 属性说明符序列(C++11 起)
- Lambda 表达式 (C++11 起)
- noexcept 说明符 (C++11 起)
- noexcept 运算符 (C++11 起)
- alignof 运算符(C++11 起)
- alignas 说明符 (C++11 起)
- 存储类说明符
- 基于范围的 for 循环 (C++11 起)
- static_assert 声明
- 隐式转换
- 代用运算符表示
- 自增/自减运算符
- 折叠表达式(C++17 起)
- 类模板实参推导(C++17 起)
- 模板形参与模板实参
- if 语句
- inline 说明符
- 结构化绑定声明 (C++17 起)
- switch 语句
- 字符字面量
- 命名空间
- 求值顺序
- 复制消除
- consteval 说明符 (C++20 起)
- constinit 说明符 (C++20 起)
- 协程 (C++20)
- 模块 (C++20 起)
- 约束与概念 (C++20 起)
- new 表达式
- do-while 循环
- continue 语句
- break 语句
- goto 语句
- return 语句
- 动态异常说明
- throw 表达式
- try 块
- 命名空间别名
- 类声明
- cv(const 与 volatile)类型限定符
- 默认初始化
- 值初始化(C++03 起)
- 零初始化
- 复制初始化
- 直接初始化
- 常量初始化
- 引用初始化
- 值类别
- C++ 运算符优先级
- 布尔字面量
- 浮点字面量
- typedef 说明符
- 显式类型转换
- static_cast 转换
- dynamic_cast 转换
- const_cast 转换
- reinterpret_cast 转换
- delete 表达式
- 构造函数与成员初始化器列表
- this 指针
- 访问说明符
- 友元声明
- virtual 函数说明符
- explicit 说明符
- 静态成员
- 默认构造函数
- 复制构造函数
- 复制赋值运算符
- 析构函数
- 类模板
- 函数模板
- 显式(全)模板特化
- 汇编声明
- C++ 的历史
- 作用域
- 生存期
- 定义与单一定义规则(ODR)
- 名字查找
- 有限定的名字查找
- 无限定的名字查找
- 如同规则
- 未定义行为
- 翻译阶段
- 常量表达式
- 赋值运算符
- 算术运算符
- 逻辑运算符
- 成员访问运算符
- 其他运算符
- sizeof 运算符
- typeid 运算符
- 指针声明
- 数组声明
- 语言链接
- 详述类型说明符
- 默认实参
- 变长实参
- 实参依赖查找
- 重载决议
- 重载函数的地址
- 注入类名
- 非静态数据成员
- 非静态成员函数
- 嵌套类
- 派生类
- 空基类优化
- 抽象类
- 位域
- 转换构造函数
- 成员模板
- 模板实参推导
- 部分模板特化
- sizeof... 运算符
- 待决名
- 函数 try 块
- 扩充命名空间 std
- 字母缩写
- RAII
- 三/五/零之法则
- PImpl
- 零开销原则
- 类型
- 隐式转换
- 注释
- C++ 关键词
- 预处理器
- C++ 标准库头文件
- 具名要求
- 功能特性测试 (C++20)
- 工具库
- 类型支持(基本类型、RTTI、类型特性)
- 概念库 (C++20)
- 错误处理
- 动态内存管理
- 日期和时间工具
- 字符串库
- 容器库
- 迭代器库
- 范围库 (C++20)
- 算法库
- 数值库
- 输入/输出库
- 文件系统库
- 本地化库
- 正则表达式库
- 原子操作库
- 线程支持库
- 实验性 C++ 特性
- 有用的资源
- 索引
- std 符号索引
- 协程支持 (C++20)
- C++ 关键词
协程 (C++20)
协程是能暂停执行以在之后恢复的函数。协程是无栈的:它们通过返回到调用方暂停执行,并且从栈分离存储恢复所要求的数据。这允许编写异步执行的顺序代码(例如不使用显式的回调来处理非阻塞 I/O),还支持对惰性计算的无限序列上的算法及其他用途。
若函数的定义做下列任何内容之一,则它是协程:
- 用
co_await
运算符暂停执行,直至恢复
task<> tcp_echo_server() { char data[1024]; for (;;) { std::size_t n = co_await socket.async_read_some(buffer(data)); co_await async_write(socket, buffer(data, n)); } }
- 用关键词
co_yield
暂停执行并返回一个值
generator<int> iota(int n = 0) { while(true) co_yield n++; }
- 用关键词 co_return 完成执行并返回一个值
lazy<int> f() { co_return 7; }
每个协程必须具有能够满足一组要求的返回类型,标注于下。
限制
协程不能使用变长实参,普通的 return 语句,或占位符返回类型(auto
或 Concept
)。
constexpr 函数、构造函数、析构函数及 main
函数 不能是协程。
执行
每个协程均与下列对象关联
- 承诺(promise)对象,从协程内部操纵。协程通过此对象提交其结果或异常。
- 协程句柄 (coroutine handle),从协程外部操纵。这是用于恢复协程执行或销毁协程帧的非拥有柄。
- 协程状态 (coroutine state),它是一个包含以下各项的分配于堆(除非优化掉其分配)的内部对象
- 承诺对象
- 各个形参(全部按值复制)
- 当前暂停点的某种表示,使得恢复时程序知晓要从何处继续,销毁时知晓有哪些局部变量在作用域内
- 局部变量和临时量,其生存期跨过当前暂停点
当协程开始执行时,它进行下列操作:
- 用 operator new 分配协程状态对象(见下文)
- 将所有函数形参复制到协程状态中:按值传递的形参被移动或复制,按引用传递的参数保持为引用(因此,若在被指代对象的生存期结束后恢复协程,其可能变为悬垂引用)
- 调用承诺对象的构造函数。若承诺类型拥有接收所有协程形参的构造函数,则以复制后的协程实参调用该构造函数。否则调用其默认构造函数。
- 调用 promise.get_return_object() 并将其结果在局部变量中保持。该调用的结果将在协程首次暂停时返回给调用方。至此并包含这个步骤为止,任何抛出的异常均传播回调用方,而非置于承诺中。
- 调用 promise.initial_suspend() 并 co_await 其结果。典型的 Promise 类型,要么(对于惰性启动的协程)返回 std::suspend_always,要么(对于急切启动的协程)返回 std::suspend_never。
- 当 co_await promise.initial_suspend() 恢复时,开始协程体的执行。
当协程抵达暂停点时
- 将先前获得的返回对象返回给调用方/恢复方,若需要则先隐式转换到协程的返回类型。
当协程抵达 co_return 语句时,它进行下列操作:
- 对下列情形调用 promise.return_void()
- co_return;
- co_return expr,其中 expr 具有 void 类型
- 控制流出返回 void 的协程的结尾。此情况下,若 Promise 类型无 Promise::return_void() 成员函数,则行为未定义。
- 或对于 co_return expr 调用 promise.return_value(expr),其中 expr 具有非 void 类型
- 以创建的逆序销毁所有具有自动存储期的变量。
- 调用 promise.final_suspend() 并 co_await 其结果。
若协程因未捕捉的异常结束,则它进行下列操作:
- 捕捉异常并在 catch 块内调用 promise.unhandled_exception()
- 调用 promise.final_suspend() 并 co_await 其结果(例如,以恢复某个继续或发布其结果)。从此点之后,恢复协程是未定义行为。
当经由 co_return
或未捕捉异常而终止协程导致协程状态被销毁,或经由其句柄而导致其被销毁时,它进行下列操作:
- 调用承诺对象的析构函数。
- 调用各个函数形参副本的析构函数。
- 调用 operator delete 以释放协程状态所用的内存。
- 转移执行回到调用方/恢复方。
堆分配
协程状态由非数组 operator new 在堆上分配。
若 Promise 类型定义了类级别的替代函数,则将使用它,否则将使用全局的 operator new。
若 Promise 类型定义了接收额外形参的 operator new 的布置形式,而它们所匹配的实参列表中,第一实参是要求的大小(std::size_t 类型),而其余则是各个协程函数实参,则将这些实参传递给 operator new(这使得能对协程使用前导分配器约定)
以下情况下,可以优化掉对 operator new 的调用(即使使用了自定义分配器):
- 协程状态的生存期严格内嵌于调用方的生存期,且
- 协程帧的大小在调用点已知
该情况下,协程状态嵌入调用方的栈帧(若调用方是普通函数)或协程状态(若调用方是协程)之中。
若分配失败,则协程抛出 std::bad_alloc,除非 Promise 类型定义了成员函数 Promise::get_return_object_on_allocation_failure()。若定义了该成员函数,则使用 operator new 的 nothrow 形式进行分配,而在分配失败时,协程立即将从 Promise::get_return_object_on_allocation_failure() 获得的对象返回给调用方。
Promise
编译器用 std::coroutine_traits 从协程的返回类型确定 Promise 类型。
若协程被定义为 task<float> foo(std::string x, bool flag);,则其 Promise 类型为 std::coroutine_traits<task<float>, std::string, bool>::promise_type。
若协程是非静态成员函数,如 task<void> my_class::method1(int x) const;,则其 Promise 类型为 std::coroutine_traits<task<void>, const my_class&, int>::promise_type。
本节未完成 |
co_await
一元运算符 co_await 暂停协程并将控制返回给调用方。其操作数是一个表达式,其类型必须要么定义 operator co_await,要么能以当前协程的 Promise::await_transform 转换到这种类型。
co_await 表达式
|
|||||||||
首先,以下列方式将 表达式 转换成可等待体(awaitable):
- 若 表达式 由初始暂停点、最终暂停点或 yield 表达式所产生,则可等待体为 表达式 本身。
- 否则,若当前协程的 Promise 类型拥有成员函数 await_transform,则可等待体为 promise.await_transform(表达式)。
- 否则,可等待体为 表达式 本身。
然后以下列方式获得等待器(awaiter)对象:
- 若针对 operator co_await 的重载决议给出单个最佳重载,则等待器是该调用的结果(对于成员重载为 awaitable.operator co_await();,对于非成员重载为 operator co_await(static_cast<Awaitable&&>(awaitable));)
- 否则,若重载决议找不到
operator co_await
,则等待器是可等待体本身 - 否则,若重载决议有歧义,则程序非良构
若上述表达式为纯右值,则等待器对象是从它实质化的临时量。否则,若上述表达式为泛左值,则等待器对象是其所指代的对象。
然后,调用 awaiter.await_ready()(这是当已知结果就绪或可以同步完成时,用以避免暂停开销的快捷方式)。若其结果按语境转换成 bool
为 false,则
- 暂停协程(以各局部变量和当前暂停点填充其协程状态)。
- 调用 awaiter.await_suspend(handle),其中 handle 是表示当前协程的协程句柄。这个函数内部可通过这个句柄观察暂停的协程,而且此函数负责调度它以在某个执行器上恢复,或将其销毁(并返回
false
当做调度)- 若
await_suspend
返回void
,则将控制立即返回给当前协程的调用方/恢复方(此协程保持暂停),否则 - 若
await_suspend
返回bool
,则
- 值为 true 时将控制返回给当前协程的调用方/恢复方
- 值为 false 时恢复当前协程。
- 若
await_suspend
返回某个其他协程的协程句柄,则(通过调用 handle.resume())恢复该句柄(注意这可以连锁进行,并最终导致当前协程恢复) - 若
await_suspend
抛异常,则捕捉该异常,恢复协程,并立即重抛异常
- 若
- 最后,调用 awaiter.await_resume(),而其结果就是整个 co_await expr 表达式的结果。
若协程在 co_await 表达式中暂停,而之后恢复,则恢复点处于紧接对 awaiter.await_resume() 的调用之前。
注意,因为协程在进入 awaiter.await_suspend() 前已完全暂停,所以该函数可以自由地在线程间转移协程柄,而无需额外同步。例如,可以将它放入回调,将它调度成在异步 I/O 操作完成时在线程池上运行等。这也表示当前协程可能并发地在这个线程池上恢复并结束,同时仍在 await_suspend()
之内,故而 await_suspend()
在将句柄发布给其他线程后不应期待其等待器(*this 对象)仍可访问。
注意:等待器对象是协程状态的一部分(作为生存期跨过暂停点的临时量),并且在 co_await 表达式结束前销毁。可以用它维护某些异步 I/O API 所要求的每操作内状态,而无需用到额外的堆分配。
标准库定义了两个平凡的可等待体:std::suspend_always 及 std::suspend_never。
本节未完成 原因:示例 |
co_yield
yield 表达式向调用方返回一个值并暂停当前协程:它是可恢复生成器函数的常用构建块
co_yield 表达式
|
|||||||||
co_yield 花括号初始化器列表
|
|||||||||
它等价于
co_await promise.yield_value(表达式)
典型的生成器的 yield_value
会将其实参存储(复制/移动或仅存储其地址,因为实参的生存期跨过 co_await 内的暂停点)到生成器对象中并返回 std::suspend_always,将控制转移给调用方/恢复方。
本节未完成 原因:示例 |
库支持
本节未完成 |