C++promise_type协程承诺类型
C++协程里那个“默默扛事”的人:promise_type到底在忙什么?
你写完 co_await,编译器却报错说 promise_type 缺失——不是漏了头文件,也不是少打了分号,而是你的协程“没配齐上岗证”。这感觉就像租好房子、买好锅碗瓢盆,结果物业说:“不好意思,您还没签《住户承诺书》。”
没错,promise_type 就是协程的“住户承诺书”:它不直接干活,但决定了协程怎么初始化、怎么返回值、怎么处理异常、甚至——协程中途挂起时,谁来保管那半截没执行完的栈帧。
很多人把它当成模板参数里的装饰品,直到第一次手写协程适配器时卡死在 get_return_object() 返回类型上,才意识到:promise_type 不是协程的说明书,而是它的操作系统内核接口。
协程函数返回类型(比如 Task<int>)本身不存储状态。真正承载协程生命周期的是编译器悄悄分配的一块内存——协程帧(coroutine frame)。而 promise_type,就是这块内存里那个被构造出来的对象,它全程坐在协程帧里,调度器每一次唤醒、挂起、销毁,都得跟它打照面。
所以,当你定义:
struct Task {
struct promise_type {
int value_;
Task get_return_object() { return {}; }
std::suspend_never initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void return_value(int v) { value_ = v; }
void unhandled_exception() { std::terminate(); }
};
};
你不是在“声明一个类型”,而是在给协程装上四只脚和一只胃:
initial_suspend()决定协程一出生就躺平(suspend_always)还是立刻开干(suspend_never);final_suspend()控制协程结束前是否允许再挂起一次——这直接关系到能否安全释放资源;return_value()是协程co_return 42;的终点站,值往哪塞,全由你定;unhandled_exception()不是兜底日志,而是协程崩溃前的最后一道闸门。
最容易踩坑的地方,其实是 get_return_object()。它返回的,必须是协程函数声明的返回类型(如 Task),但这个对象必须能持有或访问到 promise 对象本身。常见写法是让 Task 持有一个 coroutine_handle<promise_type>:
struct Task {
struct promise_type { /* ... */ };
using handle_type = std::coroutine_handle<promise_type>;
handle_type h_;
explicit Task(handle_type h) : h_(h) {}
auto get_return_object() { return Task{handle_type::from_promise(*this)}; }
};
注意:from_promise(*this) 这一行,是把当前 promise 地址转成 handle——不是凭空造 handle,而是把 promise 的地址“注册”进协程运行时系统。漏掉这步,co_await 就找不到上下文;写错类型(比如传 promise_type& 却用 handle_type::from_address),运行时直接 UB。
还有一个常被忽略的细节:await_transform。它不在 promise_type 里定义,但一旦你在 Task 里加了这个成员函数,它就会接管所有 co_await expr 的转换逻辑。而它的默认行为,恰恰依赖 promise_type::await_transform(如果存在)。这意味着:你可以用 promise_type 统一拦截所有 await 表达式,做日志、超时包装、甚至自动续期 token。
比如,在 promise_type 里加:
template<typename T>
auto await_transform(T&& t) {
static_assert(!std::is_same_v<std::decay_t<T>, Task>, "no self-await");
return TimedAwaiter{std::forward<T>(t), 5s}; // 自动包一层超时
}
从此,协程里每个 co_await http_get(...) 都自带 5 秒熔断——不用改业务代码,也不用每个 await 都手动套。
最后说个实在的调试技巧:协程挂住不动?先检查 final_suspend() 是否真的返回了 suspend_always。很多人写成 return {};,却忘了 std::suspend_always{} 才是挂起,而空大括号可能调用默认构造,生成的是 std::suspend_never(取决于标准库实现),导致协程执行完立刻析构,handle 失效,后续 resume() 变成野指针操作。
另外,promise_type 的析构函数,是你回收协程私有资源的唯一可靠时机。堆上分配的缓冲区、打开的 fd、申请的 GPU memory——别指望 Task 析构能兜住,协程帧销毁时,promise 对象才真正谢幕。
C++协程没有“运行时库”帮你兜底,promise_type 就是那个你亲手焊在协程心脏上的控制模块。它不炫技,不抢镜,但每次 co_await 转身、每次 co_return 落地、每次异常撕开调用栈,都是它在暗处稳稳接住。
写对 promise_type,协程才不是语法糖,而是你可控的、可调试的、可组合的确定性并发单元。
它不承诺让你少写代码,但它确实承诺:只要你签了这份“承诺书”,协程的每一步,都算数。


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