C++23 标准中新加入的协程实现及其实际使用

在 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_typecoroutine_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::asyncstd::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::filesystemstd::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;
}

说明

  1. async_file_reader 是一个包装器,内部使用协程读取文件内容,然后将结果返回。
  2. main 中,我们同步等待协程完成(通过调用 get()),但在实际的异步框架(如 Boost.Asio、libuv 等)中,可以把协程挂起、交给事件循环去调度。
  3. 通过 promise_typereturn_value,我们将读取到的内容存放在 promise 的成员变量 result 中。

4. 小结

  • C++23 对协程的标准库进行了完善,正式提供 std::generator 与更友好的 promise_type/coroutine_handle 接口。
  • 协程的使用不再局限于异步 I/O,还可用于生成器、协作式并发等场景。
  • 在实际项目中,建议将协程与事件循环框架结合,以充分发挥协程的优势。

通过上述内容,希望能帮助你快速上手 C++23 协程,提升代码的可读性与性能。

发表评论