在上一篇文章中,我们为协程函数单独定义了一个返回值结构体,其中必须内嵌 promise_type 并实现若干接口。然而,当项目中出现多个返回值类型不同(int、void、double 等)的协程时,为每一种返回值重复编写几乎相同的结构体会造成大量冗余代码。为了提升代码复用性,我们希望设计一个通用的协程任务类,通过模板参数来指定协程的返回值类型。同时,还需要处理”有返回值”与“无返回值”两种情况的差异:- 对于有返回值类型 T,promise_type 中需要定义 return_value(T) 和 yield_value(T),并存储一个 T 类型的 result 成员。
- 对于 void,需要定义 return_void(),且不存储结果。
1. 设计思路
- 基类 _promise_type_base:存放所有 promise_type 共有的函数(initial_suspend、final_suspend、unhandled_exception)以及异常存储。
- 派生模板类 _promise_type:处理非 void 的返回值类型,提供 return_value、yield_value 和 result 成员。
- 特化 _promise_type:处理无返回值情况,提供 return_void()。
- 通用任务类 CoroTask:对外暴露统一的接口,内部根据 promise_type 构建句柄,并封装恢复执行、获取结果等操作。
2. 完整代码实现
下面给出一个完整的通用协程任务类实现,支持任意返回值类型(包括 void)。#include<coroutine>#include<stdexcept>// 前置声明template <typename T>struct CoroTask;// ========== promise_type 基类(公共部分) ==========template <typename T>struct _promise_type_base{ // 协程初始挂起策略(可根据需要改为 suspend_never) autoinitial_suspend()noexcept{ return std::suspend_always{}; } // 协程最终挂起策略 autofinal_suspend()noexcept{ return std::suspend_always{}; } // 异常处理:保存异常指针 voidunhandled_exception()noexcept{ except = std::current_exception(); } std::exception_ptr except;};// ========== 有返回值版本的 promise_type ==========template <typename T>struct _promise_type : public _promise_type_base<T>{ // 创建协程返回值对象 CoroTask<T> get_return_object(){ return CoroTask<T>{std::coroutine_handle<_promise_type<T>>::from_promise(*this)}; } // 处理 co_yield 表达式(左值和右值重载) autoyield_value(T&& value){ this->result = std::forward<T>(value); return std::suspend_always{}; } autoyield_value(const T& value){ this->result = value; return std::suspend_always{}; } // 处理 co_return expr(有返回值) voidreturn_value(T&& value)noexcept{ result = std::forward<T>(value); } voidreturn_value(const T& value)noexcept{ result = value; } T result; // 存储协程的返回值};// ========== 无返回值特化 (void) ==========template <>struct _promise_type<void> : public _promise_type_base<void>{ CoroTask<void> get_return_object(); // 定义见下方 voidreturn_void()noexcept{} // 对应 co_return;};// ========== 通用协程任务类 ==========template<typename T>struct CoroTask{ using promise_type = _promise_type<T>; // 构造函数 explicitCoroTask(std::coroutine_handle<promise_type> h) : handle(h) {} // 禁用拷贝,支持移动 CoroTask(const CoroTask&) = delete; CoroTask& operator=(const CoroTask&) = delete; CoroTask(CoroTask&& other) noexcept : handle(other.handle) { other.handle = nullptr; } CoroTask& operator=(CoroTask&& other) noexcept { if (this != &other) { if (handle) handle.destroy(); handle = other.handle; other.handle = nullptr; } return *this; } ~CoroTask() { if (handle) handle.destroy(); } // 恢复协程执行 voidresume() { if (!handle.done()) { handle.resume(); } } // 获取结果(仅当 T 不是 void 时可用) T result()requires(!std::is_same_v<T, void>) { if (!handle.done()) { throw std::runtime_error("Coroutine not completed"); } if (handle.promise().except) { std::rethrow_exception(handle.promise().except); } return handle.promise().result; } std::coroutine_handle<promise_type> handle;};// 实现 void 特化的 get_return_object(因为此时 CoroTask<void> 尚未完整定义,放在类外)inline CoroTask<void> _promise_type<void>::get_return_object(){ return CoroTask<void>{ std::coroutine_handle<_promise_type<void>>::from_promise(*this) };}
3. 关键设计说明
3.1 基类的作用
_promise_type_base 集中定义了所有协程共有的行为:- initial_suspend():返回 std::suspend_always,协程创建后立即挂起,需要外部调用 resume() 才能启动。
- final_suspend():返回 std::suspend_always,协程结束后仍然保持挂起状态,以便外部在销毁前仍能访问结果或异常。
- unhandled_exception():捕获未处理的异常,保存到 std::exception_ptr 中,供后续 result() 函数重新抛出。
3.2 有返回值与无返回值的差异
- 非 void 版本:定义了 return_value(处理 co_return expr)和 yield_value(处理 co_yield expr),并拥有 T result 成员。
- void 特化:只定义 return_void(),不存储结果。yield_value 对于 void 没有意义,因此未提供(若在协程中使用 co_yield 会导致编译错误)。
3.3 任务类的接口
- 构造与移动语义:禁止拷贝,允许移动,确保句柄的唯一所有权。
- resume():恢复协程执行,如果协程已经结束则不做任何操作。
- result():仅当 T 不是 void 时可用(使用 C++20 的 requires 子句)。它会先检查协程是否已完成,若未完成则抛出异常;否则检查是否有保存的异常并重新抛出,最后返回结果。
4. 使用示例
下面展示了如何使用这个通用 CoroTask 类来编写具有不同返回值的协程函数。#include<iostream>#include<thread>#include<chrono>// 协程 1:返回 intCoroTask<int> coroutineFunc1(int value){ std::cout << "coroutineFunc1 start.\n"; std::this_thread::sleep_for(std::chrono::seconds(2)); int result = value * 10; co_return result; // 调用 return_value(int)}// 协程 2:无返回值CoroTask<void> coroutineFunc2(){ std::cout << "coroutineFunc2 start.\n"; std::this_thread::sleep_for(std::chrono::seconds(2)); co_return; // 调用 return_void()}intmain(){ // 创建两个协程(此时都处于初始挂起状态) auto task1 = coroutineFunc1(3); auto task2 = coroutineFunc2(); std::cout << "main tasks ...\n"; // 恢复协程执行 task1.resume(); task2.resume(); // 获取协程 1 的结果(协程 2 无结果) std::cout << "coroutineFunc1 result: " << task1.result() << "\n"; return 0;}
main tasks ...coroutineFunc1 start.coroutineFunc2 start.coroutineFunc1 result: 30
5. 总结
通过模板和特化,我们实现了一个通用的协程任务类 CoroTask,它能够:- 自动适配有返回值(T)和无返回值(void)两种协程。
- 统一处理协程的创建、挂起、恢复、结果获取和异常传播。
- 避免了为每个协程重复编写 promise_type 结构体的冗余工作。
这个通用类可以作为 C++20 协程开发的基础设施,大大简化异步任务、生成器等模式的实现。读者可以根据实际需求调整挂起策略(如将 initial_suspend 改为 suspend_never 以实现立即执行),或扩展支持 co_yield 的迭代器接口。