如何使用C语言中的20 co_yield关键字实现协程生成器?
- 内容介绍
- 文章标签
- 相关推荐
本文共计1030个文字,预计阅读时间需要5分钟。
直接说结论:
为什么 co_yield 单独写就编译不过
常见错误是这样写:
int fibonacci() { int a = 0, b = 1; while (true) { co_yield a; // ❌ 编译报错:'co_yield' cannot be used in a non-coroutine function int tmp = a; a = b; b += tmp; } }
原因很直接:co_yield 只能在协程函数里用,而协程函数的判定规则是:
- 函数体中至少出现一次
co_yield、co_await或co_return - 返回类型必须定义嵌套的
promise_type,且该类型满足协程 traits(如提供get_return_object、initial_suspend等) - 返回类型不能是
int、void、std::vector<T>这类“普通”类型
换句话说,co_yield 不是语法糖,它是协程状态机的触发器,没有配套的 promise 和 handle,它连编译都过不了。
立即学习“C++免费学习笔记(深入)”;
最简可用的 Generator 类骨架
下面这个 Generator<int> 能跑通 for (auto x : fib(10)),不含迭代器优化,但结构清晰、无冗余:
#include <coroutine> #include <exception> template <typename T> struct Generator { struct promise_type { T value_; std::suspend_always initial_suspend() { return {}; } std::suspend_always final_suspend() noexcept { return {}; } Generator get_return_object() { return Generator{handle_type::from_promise(*this)}; } void return_void() {} void unhandled_exception() { std::terminate(); } std::suspend_always yield_value(T v) { value_ = std::move(v); return {}; } }; using handle_type = std::coroutine_handle<promise_type> handle_type coro_; explicit Generator(handle_type h) : coro_(h) {} ~Generator() { if (coro_) coro_.destroy(); } struct iterator { handle_type h_; bool operator!=(const iterator& other) const { return h_.done() != other.h_.done(); } T operator*() const { return h_.promise().value_; } iterator& operator++() { h_.resume(); return *this; } }; iterator begin() { if (coro_ && !coro_.done()) coro_.resume(); return iterator{coro_}; } iterator end() { return iterator{nullptr}; } };
关键点:
-
yield_value必须返回一个 awaitable(如std::suspend_always),否则挂起失败 -
begin()里调一次resume()是为了走到第一个co_yield,否则for循环会立刻结束 -
end()返回一个空 handle,靠done()判断终止,这是范围 for 的约定
co_yield 函数怎么写才对
协程函数签名和内容必须严格匹配上面的 Generator<T> 定义:
Generator<int> fib(int n) { int a = 0, b = 1; for (int i = 0; i < n; ++i) { co_yield a; // ✅ 正确:产出值并挂起 int tmp = a; a = b; b = tmp + b; } }
使用方式:
for (int x : fib(5)) { std::cout << x << " "; // 输出:0 1 1 2 3 }
注意:
- 函数返回类型必须是
Generator<T>,不能是auto或别名(类型推导无法满足协程 trait) - 每次
co_yield后,协程挂起,局部变量(a、b、i)全部保留在协程帧中,下次恢复时继续用 - 如果想提前退出(比如异常或 break),必须确保
coro_.destroy()被调用,否则内存泄漏
容易被忽略的编译与链接细节
即使代码逻辑正确,也会在构建阶段失败:
- GCC 需加
-fcoroutines -std=c++20,Clang 对应-stdlib=libc++ -std=c++20 -fcoroutines-ts(注意 TS 后缀) - libcoro 或 libc++ 的 coroutine 支持需完整,某些旧版系统 libc++ 缺少
std::suspend_always特化 - MSVC 2022 17.9+ 默认支持,但需关闭 `/permissive-`,否则可能误报 promise 接口不全
- 不要试图用
std::shared_ptr<Generator>包装返回值——协程 handle 本身已管理内存,多一层智能指针反而干扰生命周期
最麻烦的其实是 promise_type 的成员函数签名:漏掉 noexcept(如 final_suspend)、返回类型写错(比如 yield_value 返回 void)、或忘了 unhandled_exception,都会导致编译器静默降级为普通函数,然后报一堆无关错误。
本文共计1030个文字,预计阅读时间需要5分钟。
直接说结论:
为什么 co_yield 单独写就编译不过
常见错误是这样写:
int fibonacci() { int a = 0, b = 1; while (true) { co_yield a; // ❌ 编译报错:'co_yield' cannot be used in a non-coroutine function int tmp = a; a = b; b += tmp; } }
原因很直接:co_yield 只能在协程函数里用,而协程函数的判定规则是:
- 函数体中至少出现一次
co_yield、co_await或co_return - 返回类型必须定义嵌套的
promise_type,且该类型满足协程 traits(如提供get_return_object、initial_suspend等) - 返回类型不能是
int、void、std::vector<T>这类“普通”类型
换句话说,co_yield 不是语法糖,它是协程状态机的触发器,没有配套的 promise 和 handle,它连编译都过不了。
立即学习“C++免费学习笔记(深入)”;
最简可用的 Generator 类骨架
下面这个 Generator<int> 能跑通 for (auto x : fib(10)),不含迭代器优化,但结构清晰、无冗余:
#include <coroutine> #include <exception> template <typename T> struct Generator { struct promise_type { T value_; std::suspend_always initial_suspend() { return {}; } std::suspend_always final_suspend() noexcept { return {}; } Generator get_return_object() { return Generator{handle_type::from_promise(*this)}; } void return_void() {} void unhandled_exception() { std::terminate(); } std::suspend_always yield_value(T v) { value_ = std::move(v); return {}; } }; using handle_type = std::coroutine_handle<promise_type> handle_type coro_; explicit Generator(handle_type h) : coro_(h) {} ~Generator() { if (coro_) coro_.destroy(); } struct iterator { handle_type h_; bool operator!=(const iterator& other) const { return h_.done() != other.h_.done(); } T operator*() const { return h_.promise().value_; } iterator& operator++() { h_.resume(); return *this; } }; iterator begin() { if (coro_ && !coro_.done()) coro_.resume(); return iterator{coro_}; } iterator end() { return iterator{nullptr}; } };
关键点:
-
yield_value必须返回一个 awaitable(如std::suspend_always),否则挂起失败 -
begin()里调一次resume()是为了走到第一个co_yield,否则for循环会立刻结束 -
end()返回一个空 handle,靠done()判断终止,这是范围 for 的约定
co_yield 函数怎么写才对
协程函数签名和内容必须严格匹配上面的 Generator<T> 定义:
Generator<int> fib(int n) { int a = 0, b = 1; for (int i = 0; i < n; ++i) { co_yield a; // ✅ 正确:产出值并挂起 int tmp = a; a = b; b = tmp + b; } }
使用方式:
for (int x : fib(5)) { std::cout << x << " "; // 输出:0 1 1 2 3 }
注意:
- 函数返回类型必须是
Generator<T>,不能是auto或别名(类型推导无法满足协程 trait) - 每次
co_yield后,协程挂起,局部变量(a、b、i)全部保留在协程帧中,下次恢复时继续用 - 如果想提前退出(比如异常或 break),必须确保
coro_.destroy()被调用,否则内存泄漏
容易被忽略的编译与链接细节
即使代码逻辑正确,也会在构建阶段失败:
- GCC 需加
-fcoroutines -std=c++20,Clang 对应-stdlib=libc++ -std=c++20 -fcoroutines-ts(注意 TS 后缀) - libcoro 或 libc++ 的 coroutine 支持需完整,某些旧版系统 libc++ 缺少
std::suspend_always特化 - MSVC 2022 17.9+ 默认支持,但需关闭 `/permissive-`,否则可能误报 promise 接口不全
- 不要试图用
std::shared_ptr<Generator>包装返回值——协程 handle 本身已管理内存,多一层智能指针反而干扰生命周期
最麻烦的其实是 promise_type 的成员函数签名:漏掉 noexcept(如 final_suspend)、返回类型写错(比如 yield_value 返回 void)、或忘了 unhandled_exception,都会导致编译器静默降级为普通函数,然后报一堆无关错误。

