在现代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. 代码解读
-
ImmediateAwaitable
一个无需真正等待的 awaitable,用来演示co_await的使用。它的await_ready()总是返回true,所以协程在遇到co_await ImmediateAwaitable{}时会立即继续。 -
**`Generator
`** 通过 `promise_type` 与协程句柄包装一个可迭代的生成器。迭代器实现了标准 `begin()/end()`,并在 `operator++()` 中恢复协程。协程完成时,迭代器会返回 `std::default_sentinel_t`,实现 `operator==`/`!=`。 -
promise_typeyield_value:在co_yield时被调用,将值保存在current_value_,随后协程挂起。initial_suspend与final_suspend:分别控制协程开始时是否立即挂起和结束后是否挂起。这里均设为suspend_always,保证协程在第一次co_yield之前就可以恢复。unhandled_exception:捕获协程内部抛出的异常。
-
生成器函数
int_range
用co_yield生成整数序列,并在每次co_yield前使用co_await演示协程挂起点。 -
主程序
调用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::atomic或std::mutex保证多线程安全的yield_value。
6. 小结
本文通过手写的 `Generator
` 与 `promise_type`,展示了 C++20 协程的核心机制,并给出了一个可直接编译运行的完整示例。掌握这些基础后,你可以进一步探索标准库提供的 `std::generator`(实验性)、`std::task` 或者第三方协程框架(如 Boost.Coroutine、cppcoro)来编写更复杂的异步逻辑。祝你在协程编程中玩得开心 🚀