0

0

C++怎么使用generator_C++23协程生成器教程【迭代】

尼克

尼克

发布时间:2026-02-21 09:53:13

|

642人浏览过

|

来源于php中文网

原创

c++23中需手动实现generator:基于std::coroutine_handle与自定义promise_type,显式调用next(),不可直接用于range-for;co_yield挂起点由yield_value返回awaitable的await_suspend决定;须谨慎处理生命周期与跨编译器abi差异。

c++怎么使用generator_c++23协程生成器教程【迭代】

怎么在 C++23 里写一个最简 generator

标准库没提供 generator 类型,得自己定义——但别急着抄 Boost 或第三方实现。C++23 协程规范里明确要求编译器支持 co_yield 和自定义协程句柄,std::generator 是提案(P2165),尚未进标准库(截至 GCC 14 / Clang 18 / MSVC 19.38)。所以你现在能用的,是基于 std::coroutine_handle + 自定义 promise_type 的轻量 generator 模板。

最简可用版本长这样:

template<typename T>
class generator {
  struct promise_type;
  using handle_type = std::coroutine_handle<promise_type>

public:
  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{}; }
    void return_void() {}
    auto yield_value(T v) {
      current_value = std::move(v);
      return std::suspend_always{};
    }
    void unhandled_exception() { std::terminate(); }
  };

private:
  handle_type h_;

public:
  explicit generator(handle_type h) : h_(h) {}
  ~generator() { if (h_) h_.destroy(); }
  generator(const generator&) = delete;
  generator& operator=(const generator&) = delete;
  generator(generator&& o) noexcept : h_(o.h_) { o.h_ = {}; }
  generator& operator=(generator&& o) noexcept {
    if (h_) h_.destroy();
    h_ = o.h_;
    o.h_ = {};
    return *this;
  }

  T next() {
    if (!h_ || h_.done()) throw std::runtime_error("generator exhausted");
    h_.resume();
    if (h_.done()) throw std::runtime_error("generator yielded after final suspend");
    return h_.promise().current_value;
  }
};

关键点不是“怎么写全”,而是:你必须显式调用 next(),不能直接用 range-for(除非补 begin()/end() 和迭代器);yield_value 返回 std::suspend_always 是为了确保每次 co_yield 后停住;initial_suspend 也设为 std::suspend_always,否则构造时就执行到第一个 co_yield 前,可能出未定义行为。

为什么 range-for 不能直接用 generator

因为标准 range-for 要求类型有 begin()end(),且返回的迭代器要满足 input_iterator 概念。而裸 generator 不是 range,它只是个可恢复的计算过程容器。

立即学习C++免费学习笔记(深入)”;

XPack
XPack

全球首个开源的MCP交易平台

下载
  • 强行加 begin()/end() 需额外封装一层(比如 generator_range),还要处理移动语义和多次遍历问题
  • 每次 range-for 都会尝试构造新迭代器,但 generator 是一次性消耗品(resume() 后无法 rewind)
  • 如果 generator 内部有局部变量或捕获状态,复制对象会导致悬垂引用或重复析构
  • Clang/GCC 当前对协程的 range 支持不一致:Clang 17+ 允许 for (auto x : f()) 如果 f() 返回带合适 begin/end 的类型;GCC 14 还会报 no viable conversion

所以别硬套语法糖。老老实实用 while + try/catch 更可控:

auto g = make_fibonacci();
while (true) {
  try {
    std::cout << g.next() << "\n";
  } catch (const std::runtime_error&) {
    break;
  }
}

co_yield 后挂起的位置到底在哪

很多人以为 co_yield expr 等价于 “计算 expr → 存进 promise → 挂起”,其实挂起发生在 yield_value 返回的 awaitable 的 await_suspend 执行之后。也就是说,真正暂停点取决于你 yield_value 返回的那个对象的 await_suspend 实现。

  • 返回 std::suspend_always{}:立刻挂起,控制权交还给调用方
  • 返回 std::suspend_never{}:不挂起,协程继续往下跑(危险!容易栈溢出或逻辑错乱)
  • 如果你返回自定义 awaitable,并在 await_suspend 里调度到线程池,那挂起时机就由调度器决定——这已超出 generator 基本用途
  • 调试时注意:GDB/LLDB 对协程帧支持有限,co_yield 行可能显示为 “not executable”,实际断点得打在 yield_valueawait_suspend

MSVC / GCC / Clang 在协程 ABI 上的坑

三大编译器对协程的内存布局、promise 构造时机、异常传播路径实现不一致,尤其影响 generator 的跨平台稳定性。

  • MSVC 默认把 promise 分配在协程帧内(stack-allocated),但若 promise 有非平凡析构函数,可能触发未定义行为;建议加 #pragma clang system_header 或用 /Zc:coroutines- 关闭优化干扰
  • GCC 13–14 中,若 generator 函数参数含右值引用,co_yield 可能绑定到已销毁的临时对象(生命周期分析 bug),规避方式:全部传值或 const lvalue 引用
  • Clang 17 开始支持 -fcoroutines-ts,但若混用 libstdc++(GCC 标准库)会链接失败,必须用 libc++ 编译整个项目
  • 所有编译器都不保证协程帧分配在堆上——所以不要假设 generator 对象比其内部 handle 活得久;析构顺序错位极易 crash

generator 不是语法糖,它是手动管理协程生命周期的薄封装。最易被忽略的是:你永远得自己确保 promise 的 lifetime 覆盖整个 resume 过程,而不是依赖 RAII 自动管理。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
while的用法
while的用法

while的用法是“while 条件: 代码块”,条件是一个表达式,当条件为真时,执行代码块,然后再次判断条件是否为真,如果为真则继续执行代码块,直到条件为假为止。本专题为大家提供while相关的文章、下载、课程内容,供大家免费下载体验。

103

2023.09.25

c语言const用法
c语言const用法

const是关键字,可以用于声明常量、函数参数中的const修饰符、const修饰函数返回值、const修饰指针。详细介绍:1、声明常量,const关键字可用于声明常量,常量的值在程序运行期间不可修改,常量可以是基本数据类型,如整数、浮点数、字符等,也可是自定义的数据类型;2、函数参数中的const修饰符,const关键字可用于函数的参数中,表示该参数在函数内部不可修改等等。

549

2023.09.20

堆和栈的区别
堆和栈的区别

堆和栈的区别:1、内存分配方式不同;2、大小不同;3、数据访问方式不同;4、数据的生命周期。本专题为大家提供堆和栈的区别的相关的文章、下载、课程内容,供大家免费下载体验。

421

2023.07.18

堆和栈区别
堆和栈区别

堆(Heap)和栈(Stack)是计算机中两种常见的内存分配机制。它们在内存管理的方式、分配方式以及使用场景上有很大的区别。本文将详细介绍堆和栈的特点、区别以及各自的使用场景。php中文网给大家带来了相关的教程以及文章欢迎大家前来学习阅读。

594

2023.08.10

堆和栈的区别
堆和栈的区别

堆和栈的区别:1、内存分配方式不同;2、大小不同;3、数据访问方式不同;4、数据的生命周期。本专题为大家提供堆和栈的区别的相关的文章、下载、课程内容,供大家免费下载体验。

421

2023.07.18

堆和栈区别
堆和栈区别

堆(Heap)和栈(Stack)是计算机中两种常见的内存分配机制。它们在内存管理的方式、分配方式以及使用场景上有很大的区别。本文将详细介绍堆和栈的特点、区别以及各自的使用场景。php中文网给大家带来了相关的教程以及文章欢迎大家前来学习阅读。

594

2023.08.10

线程和进程的区别
线程和进程的区别

线程和进程的区别:线程是进程的一部分,用于实现并发和并行操作,而线程共享进程的资源,通信更方便快捷,切换开销较小。本专题为大家提供线程和进程区别相关的各种文章、以及下载和课程。

695

2023.08.10

promise的用法
promise的用法

“promise” 是一种用于处理异步操作的编程概念,它可以用来表示一个异步操作的最终结果。Promise 对象有三种状态:pending(进行中)、fulfilled(已成功)和 rejected(已失败)。Promise的用法主要包括构造函数、实例方法(then、catch、finally)和状态转换。

323

2023.10.12

pixiv网页版官网登录与阅读指南_pixiv官网直达入口与在线访问方法
pixiv网页版官网登录与阅读指南_pixiv官网直达入口与在线访问方法

本专题系统整理pixiv网页版官网入口及登录访问方式,涵盖官网登录页面直达路径、在线阅读入口及快速进入方法说明,帮助用户高效找到pixiv官方网站,实现便捷、安全的网页端浏览与账号登录体验。

796

2026.02.13

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
C# 教程
C# 教程

共94课时 | 9.8万人学习

C 教程
C 教程

共75课时 | 4.9万人学习

C++教程
C++教程

共115课时 | 18.6万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号