C++ 17 中的协程: 简单使用示例

协程(Coroutines)在 C++20 标准中正式加入标准库,为异步编程提供了强大而简洁的工具。虽然在 C++17 里没有直接支持协程的语法,但通过第三方库(如 Boost.Coroutine 或 cppcoro)以及手写状态机,仍能在 C++17 环境下模拟协程的行为。下面我们通过一个简易的生成器实现,演示如何在 C++17 中用类和迭代器模式实现协程效果。

#include <iostream>
#include <functional>
#include <vector>

template <typename T>
class Generator {
public:
    using GeneratorFunc = std::function<void(std::function<void(const T&)>)>;

    explicit Generator(GeneratorFunc func) : genFunc(std::move(func)) {}

    // 内部迭代器,用于遍历生成器产出的值
    class Iterator {
    public:
        Iterator(Generator* parent, bool end = false) : parent(parent), finished(end) {
            if (!finished) {
                // 立即请求第一个值
                parent->nextValue(*this);
            }
        }

        const T& operator*() const { return value; }
        const T* operator->() const { return &value; }

        Iterator& operator++() {
            parent->nextValue(*this);
            return *this;
        }

        bool operator==(const Iterator& other) const { return finished == other.finished; }
        bool operator!=(const Iterator& other) const { return !(*this == other); }

    private:
        friend class Generator;
        Generator* parent;
        T value;
        bool finished = false;

        // 生成器从内部调用此方法获取下一个值
        void setValue(const T& val) { value = val; finished = false; }
    };

    Iterator begin() { return Iterator(this); }
    Iterator end()   { return Iterator(this, true); }

private:
    GeneratorFunc genFunc;
    std::vector <T> buffer;  // 缓存最近生成的值,支持多次遍历
    size_t bufferIndex = 0;

    void nextValue(Iterator& it) {
        if (bufferIndex < buffer.size()) {
            it.setValue(buffer[bufferIndex++]);
        } else {
            // 通过回调模式生成下一个值
            bool produced = false;
            genFunc([&](const T& val) {
                buffer.push_back(val);
                it.setValue(val);
                produced = true;
            });
            if (!produced) {
                it.finished = true;
            } else {
                bufferIndex = 1;
            }
        }
    }
};

// 使用示例:生成斐波那契数列的前10项
int main() {
    Generator <int> fib([](std::function<void(const int&)> yield) {
        int a = 0, b = 1;
        for (int i = 0; i < 10; ++i) {
            yield(a);
            int tmp = a + b;
            a = b;
            b = tmp;
        }
    });

    std::cout << "斐波那契数列前10项:\n";
    for (int n : fib) {
        std::cout << n << ' ';
    }
    std::cout << '\n';
    return 0;
}

代码要点说明

  1. Generator 类

    • 接收一个函数 GeneratorFunc,该函数内部使用 yield 回调把生成的值“投递”给外部。
    • begin()end() 方法返回自定义迭代器,满足 C++ 的范围 for 循环。
  2. Iterator 内部状态

    • finished 用来标记是否已生成完所有值。
    • setValue 供内部 nextValue 调用来更新迭代器值。
  3. 缓冲区 buffer

    • 用来缓存已生成的值,支持多次遍历。
    • bufferIndex 跟踪当前读取位置。
  4. 协程模拟

    • 通过回调 yield,在生成函数中暂停并返回下一个值,类似协程的 yield
    • 生成函数在需要停止时不调用 yield,导致 producedfalse,从而终止迭代。

进一步扩展

  • 异步读取:可以在 GeneratorFunc 内部使用 std::futurestd::async,在协程停顿时进行 IO 操作。
  • 多种数据类型:`Generator ` 可被任何可拷贝或移动类型使用,满足不同业务需求。
  • 错误处理:在 yield 里传递异常或错误码,以便外层捕获。

尽管 C++17 本身不支持 co_awaitco_yield 等语法,但通过上述模式仍能在较老的编译器中实现协程般的行为。随着 C++20 的普及,建议在新项目中直接使用原生协程语法,以获得更直观、性能更优的实现。

发表评论