如何在C++20中使用std::generator实现协程?

在C++20里,std::generator(来自 <experimental/coroutine>std::experimental::generator)提供了一个简单的协程包装器,允许你像写普通函数一样写“生成器”函数。下面给出一个完整的示例,演示如何编写一个斐波那契数列生成器,并在主程序中使用它。

#include <iostream>
#include <experimental/generator>   // C++20
using namespace std::experimental; // std::generator

// 1. 斐波那契数列生成器
generator<unsigned long long> fib(unsigned int count) {
    unsigned long long a = 0, b = 1;
    for (unsigned int i = 0; i < count; ++i) {
        co_yield a;           // 暂停并返回当前值
        std::tie(a, b) = std::make_pair(b, a + b);
    }
}

// 2. 主程序
int main() {
    const unsigned int N = 20;
    std::cout << "前" << N << "个斐波那契数:\n";
    for (auto n : fib(N)) {
        std::cout << n << ' ';
    }
    std::cout << '\n';
    return 0;
}

代码要点

  1. 协程函数

    • `generator ` 是一个返回值类型,内部已经为我们实现了迭代器。
    • co_yield 用来返回当前值,并把协程挂起。下次迭代时会从 co_yield 之后继续执行。
  2. 生成器使用

    • 通过范围 for (auto n : fib(N)) 直接迭代生成器返回的值。
    • 生成器内部管理协程状态、内存以及迭代器的推进,外部代码几乎不需要关心协程的细节。
  3. 性能与内存

    • generator 的实现通常基于 std::coroutine_handle,只在第一次调用时分配一次堆内存。
    • 对于需要大量元素但仅按需使用的场景(如大文件行读取、懒加载序列等),generator 能显著减少一次性内存占用。

进阶:与 std::ranges 结合

C++20 的 ranges 也与协程配合得非常好。你可以直接使用 std::ranges::views::iota 来生成一个无限序列,然后用自定义过滤器:

#include <iostream>
#include <experimental/generator>
#include <ranges>

generator <int> filter_even(auto&& seq) {
    for (int x : seq) {
        if (x % 2 == 0)
            co_yield x;
    }
}

int main() {
    auto evens = filter_even(std::views::iota(0) | std::views::take(20));
    for (int v : evens) std::cout << v << ' ';
}

这样就能实现“生成所有偶数,限制前 20 个”的功能,完全借助协程和 ranges。

小结

  • std::generator 是 C++20 对协程的简易封装,使用起来像普通迭代器。
  • 只需在协程体内使用 co_yield,剩余的状态管理交给标准库。
  • std::ranges 搭配可编写更高级的流式数据处理。
  • 对于需要延迟计算或生成无限序列的场景,它是一个既高效又简洁的工具。

如果你在项目中需要处理大型数据集或异步事件流,强烈建议尝试 std::generator,它能让代码既保持简洁,又获得协程带来的性能优势。

发表评论