C++20协程核心是promise_type与awaitable协议:promise_type必须提供get_return_object、initial_suspend、final_suspend;awaitable需实现await_ready、await_suspend、await_resume;coroutine_handle须安全持有,不可裸存或跨生命周期使用。

协程任务包装器必须实现 promise_type
没有 promise_type,co_await 就无法挂起、恢复或获取返回值。C++20 协程不是语法糖,而是编译器与用户约定的一套接口协议,其中 promise_type 是核心契约。
它至少要提供三个成员函数:
-
get_return_object():返回你定义的任务类型(比如Task),让co_await表达式能拿到可等待对象 -
initial_suspend():决定协程启动时是否立即挂起(通常返回suspend_always{}实现懒启动) -
final_suspend() noexcept:决定协程结束时是否挂起(必须返回suspend_always{}或suspend_never{};若返回suspend_always,后续需手动销毁帧)
漏掉任意一个,编译器会报类似 no member named 'get_return_object' in 'MyTask::promise_type' 的错误。
awaitable 对象得有 await_ready/await_suspend/await_resume
你写的任务类型(如 Task)本身不是可等待的,必须通过 operator co_await() 返回一个满足 awaitable 要求的对象——即具备这三个函数:
立即学习“C++免费学习笔记(深入)”;
-
await_ready():返回bool,指示是否可直接继续(比如已就绪的值可返回true避免挂起) -
await_suspend(coroutine_handle:关键逻辑所在。你要在这里保存h)
h(即当前协程句柄),并安排它何时被恢复(例如放进线程池、定时器队列、或直接调用h.resume()实现同步返回) -
await_resume():协程恢复后,这里返回给co_await表达式的值(类型必须匹配T)
注意:await_suspend 如果返回 void,表示你已自行调度恢复;如果返回 bool,true 表示挂起,false 表示继续执行;如果返回另一个 coroutine_handle,编译器会直接跳转到那个协程。
coroutine_handle 不能裸存、不能跨栈生命周期使用
这是最常踩的坑:把 coroutine_handle 存在局部变量里,或者不加防护地在线程间传递,极易导致悬垂句柄(dangling handle)和未定义行为。
- 协程帧(frame)分配在堆上(除非显式用
std::coroutine_traits<...>::operator new拦截),但生命周期由你管理;coroutine_handle只是轻量指针,不负责释放 - 一旦协程已结束(
final_suspend后),再调用resume()或访问其promise()就是 UB - 若需异步恢复,务必确保:① 句柄被正确持有(如用
std::shared_ptr包裹任务对象);② 恢复前检查handle.done() == false
常见症状:程序偶发崩溃、segmentation fault、或 coroutine_handle::resume(): the coroutine is already complete 这类运行时断言失败。
不要试图绕过标准协程 ABI 手写 co_await 逻辑
有人想“从零实现 co_await”,其实是误解了 C++20 协程的设计意图。co_await 本身是编译器内置机制,不可重载或替换;你能控制的,只是它背后调用的那套 promise/awaitable 接口。
真正该做的,是实现干净的 promise_type 和 awaitable 类型,而不是去碰汇编、帧布局或编译器生成的 __builtin_coro_* 内建函数。后者不仅无文档、不稳定,而且不同编译器(Clang/GCC/MSVC)差异极大。
如果你发现需要读取或修改协程帧内存布局、或依赖某个特定编译器的挂起点编号——说明设计已偏离正轨。协程的可移植性和可维护性,全系于是否严格遵循 promise/awaitable 协议。










