C++20协程需编译器支持(GCC 10+/Clang 13+/MSVC 19.28+)并启用对应标志,核心是自定义返回类型含promise_type及四个必重载函数,配合awaitable三函数实现co_await,且需手动管理内存与生命周期。

coroutine是C++20标准特性,不是第三方库
它需要编译器显式支持(GCC 10+、Clang 13+、MSVC 19.28+),且默认不开启。光有语法糖 co_await、co_yield、co_return 不代表能跑起来——缺了 promise_type 定义和内存分配器配合,编译直接报错。
常见错误现象:error: 'co_await' cannot be used in a function that does not return a coroutine type,本质是返回类型没满足“可挂起”要求(即没有嵌套合法的 promise_type)。
- 必须用
-std=c++20编译,GCC/Clang 还要加-fcoroutines - MSVC 需要
/std:c++20 /await,且项目属性里启用“C++协程支持” - 返回类型不能是内置类型或普通类;得是自定义类型,且该类型内必须定义
promise_type结构体
最简可用的 generator 实现要点
想写一个能 co_yield 整数并用 for-range 遍历的生成器?重点不在 co_yield 本身,而在 promise_type::get_return_object()、initial_suspend()、final_suspend() 和 unhandled_exception() 这四个必重载函数。
容易踩的坑:漏掉 final_suspend() 返回 suspend_always{},会导致协程销毁时试图 resume 已释放的栈帧,引发未定义行为。
-
get_return_object()必须返回一个能持有协程句柄(std::coroutine_handle)的对象 -
initial_suspend()若返回suspend_always{},协程创建后不立即执行,适合懒求值;返回suspend_never{}则立刻跑——但注意异常可能在构造时抛出 -
yield_value(int v)负责把co_yield v的值存到 generator 对象里(比如成员变量current_value),并返回suspend_always{}挂起 - 别忘了
operator bool()和operator++()等迭代器协议接口,否则 for-range 无法识别
co_await 等待的是 awaitable,不是任意对象
你不能对 std::future 直接 co_await——它没实现 await_ready()、await_suspend()、await_resume() 这三个成员函数。C++20 协程的等待机制是“鸭子类型”,靠编译期查这三者是否存在来决定能否 co_await。
典型场景:封装异步 I/O 或定时器。比如想等 100ms:co_await std::chrono::milliseconds{100} 是非法的;必须包装成一个 awaitable 类型(如 sleep_for_awaitable)。
- 第三方库如 libunifex、cppcoro 提供了开箱即用的 awaitable(
cppcoro::timer、cppcoro::task) - 自己写 awaitable 时,
await_suspend()接收std::coroutine_handle,你要负责把它保存起来,并在条件满足时调用resume() - 若
await_ready()返回 true,await_suspend()根本不会被调用——这是零开销优化的关键点
协程栈与内存管理必须手动控制
C++ 协程默认使用堆分配(调用 operator new 分配 promise 对象),且无自动回收机制。如果协程长期存活又没被显式销毁,就会泄漏。更麻烦的是:协程挂起时,局部变量存在 promise 对象里,不是栈上——所以不要在协程函数里取局部变量地址并跨挂起点使用。
性能影响明显:每次 co_await 涉及一次潜在的堆分配 + 虚函数调用(await_suspend);频繁短生命周期协程比线程/回调更重。
- 可通过重载 promise_type 的
operator new/delete使用对象池或栈内存(需确保生命周期可控) - 不要在
co_yield后继续用之前栈变量的引用或指针——它们可能已被析构 - 调试困难:GDB/LLDB 对协程帧支持有限,
bt可能只显示coroutine_handle::resume,看不到原始调用链
真正难的不是写第一个 co_yield,而是设计 promise_type 的状态流转、资源归属和取消逻辑。多数人卡在 suspend/resume 的时机不对,或者 awaitable 的 resume 调用遗漏——这些错误不报编译错误,只导致协程永远挂起或崩溃。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











