如何直观理解C++协程编程?附代码实例疑问解析
关于C++协程示例代码的疑问与核心思想理解
我已经阅读过C协程相关的教程和实现资料,但仍未完全理解,想请教如何解读C源码中的协程示例?以下为示例代码:
template<typename PromiseType> struct GetPromise { PromiseType *p_; bool await_ready() { return false; } // 表示需要调用await_suspend bool await_suspend(std::coroutine_handle<PromiseType> h) { p_ = &h.promise(); return false; // 表示最终不要挂起协程 } PromiseType *await_resume() { return p_; } }; struct ReturnObject3 { struct promise_type { unsigned value_; ReturnObject3 get_return_object() { return ReturnObject3 { .h_ = std::coroutine_handle<promise_type>::from_promise(*this) }; } std::suspend_never initial_suspend() { return {}; } std::suspend_never final_suspend() noexcept { return {}; } void unhandled_exception() {} }; std::coroutine_handle<promise_type> h_; operator std::coroutine_handle<promise_type>() const { return h_; } }; ReturnObject3 counter3() { auto pp = co_await GetPromise<ReturnObject3::promise_type>{}; for (unsigned i = 0;; ++i) { pp->value_ = i; co_await std::suspend_always{}; } } void main3() { std::coroutine_handle<ReturnObject3::promise_type> h = counter3(); ReturnObject3::promise_type &promise = h.promise(); for (int i = 0; i < 3; ++i) { std::cout << "counter3: " << promise.value_ << std::endl; h(); } h.destroy(); }
针对该代码我有以下疑问:
ReturnObject3::promise_type会在counter3()函数开始后立即创建,编译器是如何知晓要创建该类型的promise_type的?假设是通过ReturnObject3的返回类型推导,但返回类型似乎并未包含在函数签名中?- 若非协程函数,
ReturnObject3对象应在函数末尾返回,但协程中可通过协程原语随时返回。如何描述协程函数的返回类型与返回值逻辑?例如在counter3()中,第一个co_await suspend_always{}返回的ReturnObject3对象在main3()中立即被析构,那么当i=1、2…时后续的co_await suspend_always{}会返回什么? - 在
co_await GetPromise<ReturnObject3::promise_type>{}行中,为ReturnObject3::promise_type创建的handle会自动在GetPromise::await_suspend(...)中被设置,编译器是如何关联ReturnObject、GetPromise与handle的?是否存在“至少一个handle绑定到一个协程”的规则?
以上疑问仅为示例,我真正想请教的是:如何从应用代码层面直观理解C++协程编程的核心思想?
疑问解答与核心思想解析
针对三个具体疑问的解答
编译器如何识别并创建promise_type
C++协程标准明确规定:只要函数包含co_await/co_yield/co_return任意一个关键字,就会被判定为协程。此时编译器会自动查找函数返回类型中嵌套的promise_type结构——这是固定的关联规则,不需要在函数签名额外声明。找到对应类型后,编译器会在协程启动的第一时间创建promise_type实例,作为协程的核心状态载体。协程的返回类型与返回值逻辑
协程函数的返回类型本质是协程句柄的包装器,和普通函数的返回值逻辑完全不同:
- 普通函数执行到
return才生成返回值并返回;但协程启动后,会先调用promise_type的get_return_object()方法,生成返回对象(即ReturnObject3实例)并立刻返回给调用者——这就是main3()里拿到的对象,它的唯一作用是携带协程句柄,让调用者能控制协程的后续执行。 - 后续的
co_await std::suspend_always{}不会返回新的ReturnObject3,它的作用是让协程挂起,把执行权交还给调用者。此时协程的栈帧、局部变量、执行位置等状态会被自动保留,等待调用者通过句柄h()再次唤醒,继续执行循环的下一次迭代。 main3()里的ReturnObject3对象被析构不影响协程,因为它只是句柄的包装,真正的协程状态(包括promise_type实例)由协程句柄管理,只要句柄未被销毁,状态就会持续存在。
- ReturnObject、GetPromise与handle的关联规则
- 每个协程必然对应唯一的
promise_type实例,且至少存在一个std::coroutine_handle绑定到该协程——这是C++协程的核心规则,句柄是调用者控制协程的唯一入口。 - 执行
co_await时,编译器会自动将当前协程的句柄传递给可等待对象的await_suspend方法:GetPromise是符合标准的可等待对象(实现了await_ready/await_suspend/await_resume三个方法),因此编译器会生成代码把当前协程句柄作为参数传入await_suspend。而ReturnObject3是通过promise_type的get_return_object()生成的,内部的句柄直接从当前协程的promise实例创建,三者通过协程本身串联在一起。
应用层面理解C++协程的核心思想
从应用代码角度,C++协程可以简单理解为可暂停、可恢复的分段执行函数,核心是打破普通函数“一执行到底”的流程:
- 自动状态保留:协程挂起时,会自动保存当前栈帧、局部变量、执行位置等所有状态,无需手动编写状态机管理。
- 协作式调度:协程的暂停和恢复完全由代码显式控制(通过
co_await/co_yield),而非操作系统强制抢占——相当于手动把一个长函数拆分成多个执行片段,在片段之间主动切换执行权。 - promise与句柄的分工:
promise_type是协程的“状态容器”,用来存储执行过程中需要传递的数据(比如示例里的value_),同时定义协程的基础行为(如初始是否挂起、最终是否挂起)。- 协程句柄(
std::coroutine_handle)是调用者与协程交互的“遥控器”,调用者可以通过它唤醒协程、销毁协程状态,还能访问promise实例获取数据。
- 可等待对象的作用:
co_await后面的可等待对象(比如示例里的GetPromise、std::suspend_always)是协程暂停/恢复的“触发器”,它决定协程是否挂起,以及挂起时需要执行的操作(比如GetPromise的作用是获取当前协程的promise指针)。
简单来说,C++协程就是把一个函数拆成多个执行片段,通过co_await在片段间切换,每个片段执行完后把执行权交还给调用者,下次唤醒时从暂停的位置继续执行——整个过程中,函数的状态会被自动保存和恢复,无需手动管理。
内容的提问来源于stack exchange,提问作者user3059627
相关产品推荐
相关产品推荐

