在 C++20 之后,协程的标准化已经进入了一个相对成熟的阶段,但 C++23 在协程方面进一步完善了标准库,提供了更完整的工具集,并对现有的实现做了微调。下面将从协程的基础语义、标准库的新增组件、以及实际项目中的应用场景三方面进行介绍,并给出一个小例子展示如何使用 C++23 的协程来实现异步 I/O。
1. 协程的基本语义回顾
协程是一个可以挂起(co_await)和恢复(co_yield / co_return)的函数。其关键语义包括:
- 挂起:
co_await expr可以暂停协程,并将控制权交还给调用者,待expr产生值时再恢复。 - 返回值:
co_return用来返回最终结果,类似于函数返回值,但可以在协程的任何位置执行。 - 生成器:
co_yield用来产生一系列值,调用方可以通过for (auto v : generator) { … }的方式迭代。
C++23 通过对协程语义的细化,让协程更加符合实际编程需求,尤其是在异常处理和生命周期管理方面。
2. C++23 中协程相关的标准库新增
2.1 std::generator
在 C++20 中,std::generator 是一个实验性功能。C++23 将其正式纳入标准,提供了:
template<class T>
concept generator = requires (T gen) {
{ std::ranges::begin(gen) } -> std::input_iterator;
{ std::ranges::end(gen) } -> std::sentinel_t<std::ranges::iterator_t<T>>;
};
使用 std::generator 可以更简洁地定义生成器函数,例如:
std::generator <int> count_to(int n) {
for (int i = 0; i < n; ++i) co_yield i;
}
2.2 std::coroutine_handle::from_promise
C++23 强化了 promise_type 与 coroutine_handle 之间的关联。通过 std::coroutine_handle::from_promise,用户可以在 promise 对象中获取自己的 handle,进而实现更灵活的挂起/恢复逻辑。
class MyPromise {
public:
std::coroutine_handle<> self;
MyPromise() {
self = std::coroutine_handle <MyPromise>::from_promise(*this);
}
};
2.3 std::async 与 std::future 的协程友好改进
在 C++23 中,std::async 的默认启动方式(std::launch::async | std::launch::deferred)已被重新定义,以更好地配合协程。std::future 现在也支持 co_await:
std::future <int> async_add(int a, int b) {
co_return a + b;
}
使用协程直接等待 future:
int sum = co_await async_add(3, 5);
3. 实际应用示例:异步文件读取
下面给出一个使用 C++23 协程实现异步文件读取的简易示例。这里使用标准库中的 std::filesystem 与 std::futures,以及一个简单的协程包装器。
#include <iostream>
#include <fstream>
#include <string>
#include <coroutine>
#include <future>
#include <filesystem>
class async_file_reader {
public:
struct promise_type {
std::string result;
async_file_reader get_return_object() { return async_file_reader{ std::coroutine_handle <promise_type>::from_promise(*this) }; }
std::suspend_never initial_suspend() { return {}; }
std::suspend_never final_suspend() noexcept { return {}; }
void return_value(std::string&& val) { result = std::move(val); }
void unhandled_exception() { std::terminate(); }
};
using handle_type = std::coroutine_handle <promise_type>;
async_file_reader(handle_type h) : coro(h) {}
~async_file_reader() { if (coro) coro.destroy(); }
std::string get() { return coro.promise().result; }
private:
handle_type coro;
};
async_file_reader read_file_async(const std::filesystem::path& p) {
std::ifstream file(p);
if (!file.is_open())
co_return std::string("File not found");
std::string content((std::istreambuf_iterator <char>(file)),
std::istreambuf_iterator <char>());
co_return std::move(content);
}
int main() {
auto reader = read_file_async("example.txt");
std::string data = reader.get(); // 这里同步等待,实际可改为异步框架
std::cout << "File content:\n" << data << '\n';
return 0;
}
说明:
async_file_reader是一个包装器,内部使用协程读取文件内容,然后将结果返回。- 在
main中,我们同步等待协程完成(通过调用get()),但在实际的异步框架(如 Boost.Asio、libuv 等)中,可以把协程挂起、交给事件循环去调度。 - 通过
promise_type的return_value,我们将读取到的内容存放在promise的成员变量result中。
4. 小结
- C++23 对协程的标准库进行了完善,正式提供
std::generator与更友好的promise_type/coroutine_handle接口。 - 协程的使用不再局限于异步 I/O,还可用于生成器、协作式并发等场景。
- 在实际项目中,建议将协程与事件循环框架结合,以充分发挥协程的优势。
通过上述内容,希望能帮助你快速上手 C++23 协程,提升代码的可读性与性能。