### 如何在C++中实现自定义协程:一个简明的示例

在现代C++(C++20及以后)中,协程(coroutines)已成为一种强大的工具,用于编写异步代码、生成器以及其他需要暂停与恢复执行流的场景。本文将通过一个完整的、可编译的示例来演示如何实现一个简单的协程生成器,生成一系列整数并在外部进行消费。示例中将覆盖协程的基本语法、generator的自定义实现,以及协程与异常、资源管理的交互。

1. 协程基础回顾

C++20 引入了关键字 co_await, co_yield, co_return,并且标准库提供了 std::generator(实验性),但在许多编译器(如 GCC 11、Clang 13)中仍未完全实现。因此,本文将手动实现一个最小化的 generator,演示协程框架的工作原理。

核心概念:

  • promise_type:协程内部的“承诺”,负责维护协程状态并向外界暴露控制接口。
  • awaitable:协程可以等待的对象,co_await 调用此对象的 await_ready/await_suspend/await_resume
  • generator:对外提供的迭代器接口,内部使用 promise_type 来实现。

2. 代码实现

#include <iostream>
#include <coroutine>
#include <exception>
#include <utility>
#include <vector>

// ------------------------------------------------------------------
// 1. Awaitable:简单的“立即就绪”的 awaitable,示例中不做真正异步等待
struct ImmediateAwaitable {
    bool await_ready() const noexcept { return true; }
    void await_suspend(std::coroutine_handle<>) const noexcept {}
    void await_resume() const noexcept {}
};

// ------------------------------------------------------------------
// 2. Generator
template<typename T>
class Generator {
public:
    // Forward declaration of promise_type
    struct promise_type;
    using handle_type = std::coroutine_handle <promise_type>;

    explicit Generator(handle_type h) : handle_(h) {}
    Generator(const Generator&) = delete;
    Generator(Generator&& other) noexcept : handle_(other.handle_) { other.handle_ = nullptr; }
    ~Generator() { if (handle_) handle_.destroy(); }

    // Iterator
    struct Iterator {
        handle_type h_;
        Iterator(handle_type h) : h_(h) { if (h_ && !h_.done()) h_.resume(); }

        T& operator*() const { return h_.promise().current_value_; }
        T* operator->() const { return &(operator*()); }

        Iterator& operator++() {
            if (h_) h_.resume();
            return *this;
        }

        bool operator==(std::default_sentinel_t) const { return !h_ || h_.done(); }
        bool operator!=(std::default_sentinel_t) const { return h_ && !h_.done(); }
    };

    Iterator begin() { return Iterator(handle_); }
    std::default_sentinel_t end() { return {}; }

private:
    handle_type handle_;
};

// ------------------------------------------------------------------
// 3. promise_type
template<typename T>
struct Generator <T>::promise_type {
    T current_value_;
    std::exception_ptr exception_;

    // 创建协程时返回
    Generator <T> get_return_object() {
        return Generator <T>{Generator<T>::handle_type::from_promise(*this)};
    }

    // 协程开始执行
    std::suspend_always initial_suspend() noexcept { return {}; }

    // 协程结束后
    std::suspend_always final_suspend() noexcept { return {}; }

    // 异常处理
    void unhandled_exception() { exception_ = std::current_exception(); }

    // co_yield
    std::suspend_always yield_value(T value) {
        current_value_ = std::move(value);
        return {};
    }

    // co_return
    void return_void() {}
};

// ------------------------------------------------------------------
// 4. 生成器函数
Generator <int> int_range(int start, int end, int step) {
    for (int i = start; i <= end; i += step) {
        // 这里演示协程可以中途暂停,例如等待输入
        co_await ImmediateAwaitable{};
        co_yield i;
    }
}

// ------------------------------------------------------------------
// 5. 主程序
int main() {
    try {
        std::cout << "生成 1 到 10 的偶数序列:\n";
        for (int val : int_range(1, 10, 2)) {
            std::cout << val << " ";
        }
        std::cout << "\n\n" << "生成 5 到 20 的奇数序列:\n";
        for (int val : int_range(5, 20, 2)) {
            std::cout << val << " ";
        }
        std::cout << "\n";
    } catch (const std::exception& ex) {
        std::cerr << "异常: " << ex.what() << '\n';
    }
    return 0;
}

3. 代码解读

  1. ImmediateAwaitable
    一个无需真正等待的 awaitable,用来演示 co_await 的使用。它的 await_ready() 总是返回 true,所以协程在遇到 co_await ImmediateAwaitable{} 时会立即继续。

  2. **`Generator

    `** 通过 `promise_type` 与协程句柄包装一个可迭代的生成器。迭代器实现了标准 `begin()/end()`,并在 `operator++()` 中恢复协程。协程完成时,迭代器会返回 `std::default_sentinel_t`,实现 `operator==`/`!=`。
  3. promise_type

    • yield_value:在 co_yield 时被调用,将值保存在 current_value_,随后协程挂起。
    • initial_suspendfinal_suspend:分别控制协程开始时是否立即挂起和结束后是否挂起。这里均设为 suspend_always,保证协程在第一次 co_yield 之前就可以恢复。
    • unhandled_exception:捕获协程内部抛出的异常。
  4. 生成器函数 int_range
    co_yield 生成整数序列,并在每次 co_yield 前使用 co_await 演示协程挂起点。

  5. 主程序
    调用 int_range 并遍历输出结果。若协程内部抛异常,外层 try/catch 处理。

4. 编译与运行

使用支持 C++20 协程的编译器(GCC 11+, Clang 13+, MSVC 16.8+):

g++ -std=c++20 -pthread -o generator_demo generator_demo.cpp
./generator_demo

输出示例:

生成 1 到 10 的偶数序列:
1 3 5 7 9 

生成 5 到 20 的奇数序列:
5 7 9 11 13 15 17 19 

说明:co_yield 产生的值在迭代器的 operator*() 里被读取,迭代结束时协程会被销毁。

5. 进一步扩展

  • 异步协程:将 ImmediateAwaitable 替换为真正等待 I/O 的 awaitable(如 std::future 或自定义 sleep_for),实现非阻塞的协程。
  • 错误传播:在 generator 迭代器中捕获 promise_type.exception_,在 operator++ 里抛出。
  • 多产线:使用 std::atomicstd::mutex 保证多线程安全的 yield_value

6. 小结

本文通过手写的 `Generator

` 与 `promise_type`,展示了 C++20 协程的核心机制,并给出了一个可直接编译运行的完整示例。掌握这些基础后,你可以进一步探索标准库提供的 `std::generator`(实验性)、`std::task` 或者第三方协程框架(如 Boost.Coroutine、cppcoro)来编写更复杂的异步逻辑。祝你在协程编程中玩得开心 🚀

发表评论