在 C++20 之前,异步编程通常依赖回调、状态机或者第三方库(如 Boost.Asio、libuv)。C++20 标准引入了协程(Coroutines)这一核心语言特性,提供了一套完整且类型安全的机制来写异步代码、生成器以及其他延迟计算。下面我们从概念、语法和实践三个方面快速上手。
1. 协程的基本概念
协程是可挂起的函数。与普通函数不同,协程可以在执行过程中暂停(co_await、co_yield 或 co_return),并在需要时恢复。协程的状态会被保存在一个隐藏的“协程对象”中,编译器负责生成状态机。
co_await:挂起协程,等待异步操作完成后继续。co_yield:生成值并暂停,类似于生成器。co_return:结束协程并返回最终值。
2. 关键头文件和类型
- ` `:定义 `std::coroutine_handle`、`std::suspend_always`、`std::suspend_never` 等。
- ` `:演示输出。
#include <coroutine>
#include <iostream>
#include <string>
3. 简单协程返回 std::string
下面的例子演示一个简单的协程,它返回一个字符串。
struct ReturnString {
struct promise_type {
std::string result;
std::string get_return_object() { return std::string(result); }
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void return_value(std::string value) { result = std::move(value); }
void unhandled_exception() { std::terminate(); }
};
};
ReturnString get_greeting() {
co_return std::string("Hello, C++ Coroutines!");
}
使用方式:
int main() {
std::string greeting = get_greeting();
std::cout << greeting << std::endl;
}
4. 生成器示例
协程最直观的用法是生成器。下面的代码演示生成 1~10 的数字。
struct Generator {
struct promise_type {
int current_value;
Generator get_return_object() { return Generator{ std::coroutine_handle <promise_type>::from_promise(*this) }; }
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
std::suspend_always yield_value(int value) {
current_value = value;
return {};
}
void return_void() {}
void unhandled_exception() { std::terminate(); }
};
std::coroutine_handle <promise_type> handle;
explicit Generator(std::coroutine_handle <promise_type> h) : handle(h) {}
~Generator() { if (handle) handle.destroy(); }
struct iterator {
std::coroutine_handle <promise_type> handle;
bool operator!=(std::default_sentinel_t) { return !handle.done(); }
void operator++() { handle.resume(); }
int operator*() const { return handle.promise().current_value; }
};
iterator begin() { handle.resume(); return {handle}; }
std::default_sentinel_t end() { return {}; }
};
Generator counter() {
for (int i = 1; i <= 10; ++i)
co_yield i;
}
使用:
int main() {
for (int n : counter())
std::cout << n << ' ';
}
5. 与异步 I/O 的结合
在实际项目中,协程往往与事件循环结合。下面给出一个基于 std::future 的异步等待示例(伪代码,实际使用时需配合 I/O 库)。
#include <future>
std::future <int> async_operation();
int main() {
auto fut = async_operation(); // 异步任务
auto co_task = [&]() -> std::future <int> {
int result = co_await fut; // 等待完成
return result * 2;
}();
std::cout << co_task.get() << std::endl;
}
6. 常见陷阱与建议
- 记得销毁协程句柄:若协程未自动销毁,手动
handle.destroy()。 - 异常安全:
promise_type::unhandled_exception必须处理,否则会terminate()。 - 返回值类型:协程函数的返回类型是
promise_type::get_return_object生成的对象,常见为std::future、自定义Generator等。
7. 结语
C++20 的协程为异步编程提供了语言级别的支持,简化了回调地狱,提升了代码可读性。虽然刚开始阅读协程相关的标准库代码可能略显复杂,但掌握了基本概念后,协程的使用会像普通函数一样自然。赶紧在自己的项目中尝试一次吧,感受一下“暂停”与“恢复”的魅力。