C++20协程实现生成器始终无法终止问题排查
问题分析与修复
你的Generator协程类无法正确判断迭代结束的核心问题出在promise_type的final_suspend实现,同时迭代逻辑也存在顺序错误,以下是具体说明和修复方案:
错误原因
final_suspend返回值错误
你当前的final_suspend()返回std::suspend_never,这意味着协程执行完最后一个co_yield后会立即销毁自身,不会进入挂起状态。而std::coroutine_handle::done()只有在协程处于完成且挂起的状态时才会返回true,所以你的handle永远无法触发done()为true的判断。迭代逻辑顺序错误
当前的循环中,你先检查gen(调用operator bool()),然后取值,再调用++gen恢复协程。但正确的顺序应该是:恢复协程→检查是否完成→取值,否则会导致最后一次取值后协程结束,但循环仍会继续执行。
修复后的代码
#include <iostream> #include <coroutine> template <typename T> class Generator { public: struct promise_type { T value; std::suspend_always yield_value(T v) { value = v; return {}; } std::suspend_always initial_suspend() { return {}; } // 改为初始挂起,符合常规生成器行为 std::suspend_always final_suspend() noexcept { return {}; } // 关键修改:协程结束时挂起 void return_void() {} void unhandled_exception() { std::terminate(); } Generator get_return_object() { return Generator{ Handle::from_promise(*this) }; } }; struct Handle : std::coroutine_handle<promise_type> { Handle(std::coroutine_handle<promise_type> h) : std::coroutine_handle<promise_type>(h) {} T& operator*() { return this->promise().value; } // 改用operator*更符合迭代器习惯 void operator++() { this->resume(); } bool operator==(std::nullptr_t) const { return this->done(); } bool operator!=(std::nullptr_t) const { return !this->done(); } }; Generator(Handle h) : handle(h) {} ~Generator() { if (handle) handle.destroy(); } // 实现迭代器接口,支持范围for循环 Handle begin() { handle.resume(); // 第一次恢复协程到第一个yield点 return handle; } std::nullptr_t end() { return nullptr; } // 兼容原有代码保留的运算符 T& operator()() { return handle.promise().value; } void operator++() { ++handle; } explicit operator bool() { return !handle.done(); } private: Handle handle{}; }; Generator<int> generateNumbers(int start, int end) { for (int i = start; i <= end; ++i) { co_yield i; } } int main() { // 推荐使用范围for循环,更符合C++习惯 auto gen = generateNumbers(1, 10); for (auto num : gen) { std::cout << num << std::endl; } // 兼容原有写法的修复版本 /* auto gen = generateNumbers(1, 10); ++gen; // 先恢复到第一个yield点 while (gen) { std::cout << gen() << std::endl; ++gen; } */ }
关键修改点说明
- 将
final_suspend()的返回值改为std::suspend_always,确保协程执行完毕后进入挂起状态,此时handle.done()会正确返回true。 - 将
initial_suspend()改为std::suspend_always,这是生成器的常规行为:创建后不立即执行,等待第一次resume才开始生成值。 - 新增了完整的迭代器接口,支持范围for循环,代码更简洁易读。
- 调整了原有循环的执行顺序:先调用
++gen恢复协程,再判断是否完成,避免无效的取值操作。
内容的提问来源于stack exchange,提问作者Markus Fjellheim
相关产品推荐
相关产品推荐

