C++co_return协程返回值处理

2026-04-11 20:45:29 566阅读 0评论

co_return 不是“return”,它在替你悄悄打包快递

写协程时,第一次看到 co_return 42;,我下意识想:这不就是带个 co_ 前缀的 return 吗?结果调试半天,发现值没传出去,await_resume() 里拿到的是个空壳——原来,co_return 的真正工作,是调用 promise 对象的 return_value()return_void(),再由它决定怎么把结果塞进协程状态机里。它不直接返回,它是在调度、委托、封装。

很多人卡在这一步:协程函数声明了返回 Task<int>co_return 123; 写得挺顺,可一跑起来,Task 里存的值却是未定义的。问题往往不出在 Task 实现上,而出在 promise_type 没配对——co_return 的语义完全由 promise_type::return_value(T&&) 是否存在、是否正确实现来定义

举个具体例子。假设你写了这样一个协程:

Task<int> get_answer() {
    co_return 42;
}

编译器看到 co_return 42;,会立刻去找 Task<int>::promise_type 里有没有 return_value(int&&) 成员函数。如果有,就调用它;如果没有,但 42void 类型,它才退而求其次找 return_void();如果两个都没有,编译直接报错:“no matching function for call to ‘return_value’”。

这里有个容易被忽略的细节:co_return expr; 传递给 return_value 的实参类型,取决于 expr 的值类别和 return_value 的形参签名。比如:

  • co_return 42; → 传 int&&(纯右值)
  • co_return x;(x 是 int 变量)→ 传 int&,除非你显式写 co_return std::move(x);
  • 如果 promise_type::return_value 只声明了 void return_value(int&&),那 co_return x; 就会编译失败。

所以,别急着写 co_return,先盯住你的 promise_type。常见错误是只实现了 return_void()(比如协程不返回值),却在需要返回值的地方硬写 co_return val;——这不是语法错,是语义断层。

更实际的问题是:如何让 co_return 把值安全地“交到用户手上”?
关键在 promise_type 怎么存这个值,以及 Taskawait_resume() 怎么取。典型做法是:在 promise 对象里加一个 std::optional<T> result_return_value 负责移动构造进去;await_resume()return std::move(result_).value();。但注意——await_resume() 必须在协程已结束(done() == true)后才被调用,否则 result_ 可能还没被赋值

还有一种轻量级方案:不存值,而是把值直接塞进 Task 对象本身。比如让 Task<T> 的构造函数接收 promise_type*,并在内部通过 promise.get_return_object_on_allocation() 或类似机制,在分配时就把 T 的存储布局预留好。这种写法性能更好,但要求 promise_typeTask 紧密耦合,适合自研协程库,不适合快速原型。

顺便提一句:co_return;(无表达式)和 co_return expr; 是两条不同的路径。前者强制走 return_void(),后者走 return_value()。哪怕你的协程逻辑上“不返回有意义的值”,只要函数签名是 Task<int>,就必须提供 return_value(int&&)——哪怕只是 static_assert(false, "should not happen");,也比漏掉强。编译器不会帮你“猜意图”,它只认签名。

最后,一个真实踩坑场景:异常传播。如果 co_return expr; 中的 expr 构造过程抛异常(比如 std::string 移动构造时 OOM),这个异常会绕过 return_value(),直接进入协程的异常处理流程(触发 unhandled_exception())。这意味着——co_return 不是原子操作,它的“返回”可能被异常截断,return_value() 根本不会执行。所以,如果你在 return_value() 里做了资源登记(比如记录日志、更新计数器),要意识到它并不总被执行。

总结下来,co_return 是协程世界的“交接班口”:它不负责干活,但决定谁来接活、怎么交、交什么。写协程不是堆语法糖,而是设计一套值的生命周期契约。你每写一行 co_return,都在和 promise_type 签一份协议——协议写清楚了,协程才真正可控;协议模糊了,调试时看到的就全是“值消失了”的幻觉。

下次 co_return 不生效,别急着查 Taskawait_ready(),先翻出 promise_type,一行行核对:return_value 存在吗?签名匹配吗?值真的被存进去了吗?await_resume() 拿的是同一个地方吗?——协程的确定性,就藏在这些不起眼的成员函数里。

文章版权声明:除非注明,否则均为Dark零点博客原创文章,转载或复制请以超链接形式并注明出处。

发表评论

快捷回复: 表情:
验证码
评论列表 (暂无评论,566人围观)

还没有评论,来说两句吧...

目录[+]