C++协程初始await的await-ready/await-suspend异常捕获位置
异常的正确捕获位置
对于初始await表达式的await-ready或await-suspend阶段抛出的异常,应该在启动协程的调用方(比如main函数)的catch块中捕获,而非协程体内部的异常处理逻辑。
标准条款与行为逻辑
根据C++标准定义的协程执行框架:
{
promise-type promise promise-constructor-arguments ;
try {
co_await promise.initial_suspend() ;
function-body
} catch ( ... ) {
if (!initial-await-resume-called)
throw ;
promise.unhandled_exception() ;
}
final-suspend :
co_await promise.final_suspend() ;
}
其中initial-await-resume-called初始为false,仅在await-resume表达式求值前才会被设为true。当异常发生在await-ready或await-suspend阶段时,这个标志仍为false,协程体的catch块会直接将异常重新抛出,最终传播到启动协程的调用上下文。
同时标准明确规定:若await-suspend求值因异常退出,异常会被捕获、协程恢复后立即重抛——这里的"恢复"就是回到调用方的执行上下文,因此异常会被调用方的catch块捕获。
编译器行为正确性
Clang的行为符合C++标准的要求,而GCC 12.2.1出现的双free错误属于编译器实现bug,大概率是在处理异常时错误地重复释放了协程帧资源。该问题在后续GCC版本(如13及以上)中已被修复,建议升级编译器版本验证。
正确捕获的代码示例
以下是完整的可测试代码,展示如何在调用方捕获初始await阶段的异常:
#include <coroutine> #include <iostream> // 会在await_suspend抛出异常的awaiter struct Thrower { bool await_ready() const { return false; } void await_suspend(std::coroutine_handle<>) const { throw 2; } void await_resume() const {} }; // 协程promise类型 struct Promise { Thrower initial_suspend() { return {}; } auto final_suspend() noexcept { return std::suspend_never{}; } void unhandled_exception() { std::terminate(); } void return_void() {} struct promise_type : Promise { auto get_return_object() { return std::coroutine_handle<promise_type>::from_promise(*this); } }; }; using CoroHandle = std::coroutine_handle<Promise::promise_type>; // 协程函数 CoroHandle test_coro() { co_return; } int main() { try { auto coro = test_coro(); coro.resume(); // 触发初始await的await_suspend,抛出异常 coro.destroy(); } catch (int e) { std::cout << "捕获到异常:" << e << std::endl; } return 0; }
运行这段代码(用Clang或新版GCC),main函数的catch块会成功捕获到await_suspend中抛出的异常。
内容的提问来源于stack exchange,提问作者Enlico

