C++20 协程(Coroutines)入门

在 C++20 之前,异步编程通常依赖回调、状态机或者第三方库(如 Boost.Asio、libuv)。C++20 标准引入了协程(Coroutines)这一核心语言特性,提供了一套完整且类型安全的机制来写异步代码、生成器以及其他延迟计算。下面我们从概念、语法和实践三个方面快速上手。

1. 协程的基本概念

协程是可挂起的函数。与普通函数不同,协程可以在执行过程中暂停(co_awaitco_yieldco_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. 常见陷阱与建议

  1. 记得销毁协程句柄:若协程未自动销毁,手动 handle.destroy()
  2. 异常安全promise_type::unhandled_exception 必须处理,否则会 terminate()
  3. 返回值类型:协程函数的返回类型是 promise_type::get_return_object 生成的对象,常见为 std::future、自定义 Generator 等。

7. 结语

C++20 的协程为异步编程提供了语言级别的支持,简化了回调地狱,提升了代码可读性。虽然刚开始阅读协程相关的标准库代码可能略显复杂,但掌握了基本概念后,协程的使用会像普通函数一样自然。赶紧在自己的项目中尝试一次吧,感受一下“暂停”与“恢复”的魅力。

发表评论