如何使用C语言中的20 co_yield关键字实现协程生成器?

2026-04-30 19:531阅读0评论SEO资讯
  • 内容介绍
  • 文章标签
  • 相关推荐

本文共计1030个文字,预计阅读时间需要5分钟。

如何使用C语言中的20 co_yield关键字实现协程生成器?

直接说结论:

为什么 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_yieldco_awaitco_return
  • 返回类型必须定义嵌套的 promise_type,且该类型满足协程 traits(如提供 get_return_objectinitial_suspend 等)
  • 返回类型不能是 intvoidstd::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 后,协程挂起,局部变量(abi)全部保留在协程帧中,下次恢复时继续用
  • 如果想提前退出(比如异常或 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,都会导致编译器静默降级为普通函数,然后报一堆无关错误。

标签:C

本文共计1030个文字,预计阅读时间需要5分钟。

如何使用C语言中的20 co_yield关键字实现协程生成器?

直接说结论:

为什么 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_yieldco_awaitco_return
  • 返回类型必须定义嵌套的 promise_type,且该类型满足协程 traits(如提供 get_return_objectinitial_suspend 等)
  • 返回类型不能是 intvoidstd::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 后,协程挂起,局部变量(abi)全部保留在协程帧中,下次恢复时继续用
  • 如果想提前退出(比如异常或 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,都会导致编译器静默降级为普通函数,然后报一堆无关错误。

标签:C