协程异常必须由promise_type::unhandled_exception()捕获并保存,否则直接terminate;调用方需在await_resume()或get()中主动rethrow异常。

协程函数返回类型必须支持异常传播
协程体里 throw 的异常不会自动冒泡到调用方,除非你用的返回类型(如 std::future、task<T> 自定义类型)在 promise_type::unhandled_exception() 中显式保存了异常。标准库的 std::generator 和裸 coroutine_handle 默认不捕获——它直接终止程序(类似未捕获的普通线程异常)。
常见错误现象:std::terminate 被调用,控制台只打印 “terminate called after throwing an instance of '…'”,没有栈回溯,也抓不到。
- 用
std::future包装协程:调用get()时才重新抛出(延迟传播) - 自定义
task<T>类型时,必须在promise_type::unhandled_exception()里调用std::current_exception()存起来 - 别依赖
co_await表达式本身“吞掉”异常——它只是把异常传给 promise,后续怎么处理全看 promise 实现
promise_type::unhandled_exception() 是唯一入口点
这是协程异常处理的守门人。只要协程函数体内有未被 try/catch 捕获的异常,C++20 运行时就会调用该函数。它不接收参数,也不允许返回值,唯一能做的就是记录异常或终止进程。
典型写法:
立即学习“C++免费学习笔记(深入)”;
void unhandled_exception() {
ex_ = std::current_exception(); // ex_ 是 promise_type 成员变量
}
注意:std::current_exception() 必须在此刻调用;等协程恢复后再取,可能已失效或为空。
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
- 如果什么也不做(空实现),默认行为是调用
std::terminate() - 不能在这里
rethrow——协程栈已开始销毁,此时抛出会直接 terminate - 若用
std::optional<std::exception_ptr>存异常,记得在get_return_object()返回前确保对象可访问(避免悬垂引用)
调用方如何安全获取并重抛异常
异常不是“自动抛出”,而是由协程返回对象负责暴露。比如 task<int> 对象在 co_await 完成后,其内部 promise 若存了异常,则 await_resume() 应检测并 rethrow。
示例逻辑(简化):
T await_resume() {
if (p_->ex_) {
std::rethrow_exception(p_->ex_);
}
return p_->result_;
}
关键点在于:异常传播时机完全由你控制。你可以选择延迟(如 task::get())、忽略(不检查)、或立即响应(await_resume 中 rethrow)。
- 用
std::future时,co_await不会抛异常,但future.get()会 - 自己实现
awaitable时,await_resume()是唯一能自然 rethrow 的地方 - 不要在
await_ready()或await_suspend()里处理异常——它们运行在“挂起前”或“调度中”,此时异常尚未发生
调试时容易漏掉的两个细节
一是忘记在 promise_type 构造函数中初始化异常存储成员(如 ex_{}),导致未定义行为;二是误以为 co_await 表达式周围的 try/catch 能捕获协程体内的异常——它只能捕获 await_suspend 或 await_resume 抛的,对协程体里的 throw 无效。
最稳妥的验证方式:在协程体开头加 throw std::runtime_error("test");,然后观察调用链是否真走到你的 unhandled_exception(),再确认 await_resume() 或对应 getter 是否触发 rethrow。

















