不能直接用 std::coroutine_handle 写生成器,因为它是无栈协程句柄,不保存局部变量、不管理栈帧,也不能自动挂起在任意表达式中间;必须手动实现 promise_type、yield_value、内存分配等完整契约。

为什么不能直接用 std::coroutine_handle 写生成器
因为 C++20 的协程是「无栈协程」,std::coroutine_handle 本身不保存局部变量、不管理栈帧,也不能自动挂起在任意表达式中间——你得自己写 promise_type、处理 yield_value、定义 get_return_object,还要手动分配内存(比如用 operator new)来保活 promise 对象。很多初学者一上来就套用标准模板,结果迭代时 resume() 崩溃或 yield 值错乱,根本原因是 promise 生命周期没对齐。
generator<t></t> 最小可行实现要覆盖哪几个关键点
一个能跑通的生成器必须解决三件事:怎么产出值、怎么控制生命周期、怎么支持范围 for。核心不是协程语法,而是 promise 的行为契约:
-
yield_value(T&& v)必须返回suspend_always{},否则协程不会停在co_yield -
initial_suspend()返回suspend_always{},让生成器创建后不立刻执行,等第一次++it才启动 -
final_suspend()返回suspend_always{},防止协程销毁 promise,否则迭代器解引用会读到已释放内存 - 迭代器内部必须持有
coroutine_handle<promise_type></promise_type>,且只在operator++中调用resume(),并在done()为 true 时停止
示例骨架(省略内存管理细节):
template <typename t>
struct generator {
struct promise_type {
T current_value;
auto get_return_object() { return generator{handle_type::from_promise(*this)}; }
auto initial_suspend() { return std::suspend_always{}; }
auto final_suspend() noexcept { return std::suspend_always{}; }
auto yield_value(T&& v) { current_value = std::move(v); return std::suspend_always{}; }
void return_void() {}
void unhandled_exception() { std::terminate(); }
};
using handle_type = std::coroutine_handle<promise_type>
// ... 迭代器定义、begin/end 等
};</promise_type></typename>
范围 for 里 co_yield 不触发或崩溃的常见原因
最常踩的坑不是语法写错,而是生成器对象被提前析构,或者协程 handle 没正确传递给迭代器:
- 如果
generator构造函数接收的是临时handle_type,但没用std::move转移所有权,迭代器持有的 handle 就是悬空的 - 忘记在
iterator::operator++里检查handle.done(),导致 resume 已结束的协程,UB -
co_yield后没再 co_await 或 co_return,协程卡在 final_suspend,但迭代器以为还有值,继续 ++ 就崩 - 使用
std::vector<generator>></generator>时,generator 移动构造没正确转移 handle,旧对象析构时把 promise 删了,新对象的 handle 指向野内存
验证方法:在 promise_type 的析构函数里打日志,看它是否在迭代中途被调用。
要不要自己管理堆内存,还是用 std::optional + 栈分配
用 std::optional<promise_type></promise_type> 把 promise 放栈上听起来干净,但不行——协程挂起时,栈帧可能已退出,promise 必须长期存活。所以必须堆分配:
- 重载
promise_type::operator new是最可控的方式,配合 placement new 和显式destroy() - 别依赖 RAII 自动释放:generator 析构时需显式调用
handle.destroy(),否则 promise 泄漏 - 如果只是做 demo 或短生命周期生成器(比如单次 range-for),可以用
std::unique_ptr<promise_type></promise_type>管理,但注意 move 构造/赋值必须同步转移指针和 handle
真正难的不是写出能编译的 generator,而是让它的拷贝、移动、异常安全、多线程访问(即使不并发调用)都行为确定——这些细节在标准库还没提供 std::generator 前,没人能绕过去。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











