**Exploring C++20’s Coroutine Feature: A Hands-On Guide**

Coroutines are a major addition to the C++20 language, providing a powerful mechanism for suspending and resuming function execution without the overhead of traditional threading or the complexity of state machines. In this article we’ll dive into how to declare, use, and implement coroutines in C++, focusing on practical examples that illustrate the concepts clearly.

1. What Are Coroutines?

A coroutine is a function that can suspend its execution (co_await, co_yield, co_return) and later resume from the point of suspension. Unlike generators in other languages, C++ coroutines can return values (co_return), yield intermediate values (co_yield), or even be awaited by other coroutines (co_await).

The compiler transforms a coroutine into a state machine that manages the stack frame, suspension points, and the coroutine’s promise object. As developers, we interact with the coroutine through its return type, which is typically a custom type that implements the coroutine handle.

2. The Basic Building Blocks

A minimal coroutine requires three elements:

  1. Coroutine function: marked by the return type co_return or co_yield.
  2. Promise type: defines what happens when the coroutine is started, suspended, and finished.
  3. Awaitable type: objects that can be awaited.

Let’s see a simple example that yields Fibonacci numbers.

#include <coroutine>
#include <iostream>
#include <vector>

struct Fibonacci {
    struct promise_type;
    using handle_type = std::coroutine_handle <promise_type>;

    struct promise_type {
        std::vector <int> values;

        Fibonacci get_return_object() {
            return Fibonacci{handle_type::from_promise(*this)};
        }
        std::suspend_always initial_suspend() { return {}; }
        std::suspend_always final_suspend() noexcept { return {}; }

        void unhandled_exception() { std::terminate(); }

        std::suspend_always yield_value(int v) {
            values.push_back(v);
            return {};
        }
    };

    handle_type coro;
    Fibonacci(handle_type h) : coro(h) {}
    ~Fibonacci() { coro.destroy(); }

    std::vector <int> get_values() { return coro.promise().values; }
};

Fibonacci fib(int n) {
    int a = 0, b = 1;
    for (int i = 0; i < n; ++i) {
        co_yield a;
        int next = a + b;
        a = b;
        b = next;
    }
}

Calling fib(10) returns a Fibonacci object containing the first ten Fibonacci numbers. The coroutine suspends at each co_yield, storing the value, then resumes until the loop terminates.

3. Awaitables and co_await

co_await pauses a coroutine until the awaited expression completes. The awaited type must provide a await_ready, await_suspend, and await_resume interface.

A classic example is a simple async timer:

#include <chrono>
#include <thread>
#include <future>

struct SleepAwaitable {
    std::chrono::milliseconds ms;

    bool await_ready() const noexcept { return false; }
    void await_suspend(std::coroutine_handle<> h) {
        std::thread([h, ms = ms]() {
            std::this_thread::sleep_for(ms);
            h.resume();
        }).detach();
    }
    void await_resume() noexcept {}
};

auto sleep_for(std::chrono::milliseconds ms) {
    return SleepAwaitable{ms};
}

Now we can write:

#include <iostream>
#include <chrono>
#include <coroutine>

struct AwaitableTask {
    struct promise_type {
        AwaitableTask get_return_object() { return {}; }
        std::suspend_never initial_suspend() { return {}; }
        std::suspend_never final_suspend() noexcept { return {}; }
        void unhandled_exception() { std::terminate(); }
        void return_void() {}
    };
};

AwaitableTask asyncExample() {
    std::cout << "Before sleep\n";
    co_await sleep_for(std::chrono::seconds(1));
    std::cout << "After 1 second\n";
}

Executing asyncExample() will print the two messages with a one‑second pause in between.

4. Custom Awaitable: A Simple Future

C++20 introduced std::future and std::promise which can be used as awaitables. However, you can also create lightweight custom futures:

#include <coroutine>
#include <optional>

template<typename T>
struct SimpleFuture {
    struct promise_type;
    using handle_type = std::coroutine_handle <promise_type>;

    struct promise_type {
        std::optional <T> value;
        SimpleFuture get_return_object() {
            return SimpleFuture{handle_type::from_promise(*this)};
        }
        std::suspend_never initial_suspend() noexcept { return {}; }
        std::suspend_always final_suspend() noexcept { return {}; }
        void unhandled_exception() { std::terminate(); }
        template<typename U>
        void return_value(U&& v) { value.emplace(std::forward <U>(v)); }
    };

    handle_type coro;
    SimpleFuture(handle_type h) : coro(h) {}
    ~SimpleFuture() { coro.destroy(); }

    T get() { coro.resume(); return *coro.promise().value; }
};

SimpleFuture <int> computeAsync() {
    co_return 42;  // Simulate some heavy computation
}

computeAsync().get() will resume the coroutine and return the value 42.

5. Practical Use Cases

  1. Asynchronous I/O – Combine coroutines with non‑blocking sockets or file descriptors to write clean async code.
  2. Pipeline Processing – Coroutines can be used to build streaming pipelines where each stage is a coroutine yielding intermediate results.
  3. Lazy Evaluation – Coroutines allow implementing generators that produce values on demand without storing the entire sequence.
  4. Concurrency Abstractions – Wrap thread pools or task schedulers around coroutine handles for efficient parallelism.

6. Common Pitfalls

  • Lifetime Management – Ensure coroutine handles are destroyed; otherwise, memory leaks occur.
  • Stack Overflow – Deep recursion in coroutines can still cause stack overflow if not carefully managed.
  • Awaiting on Unfinished Coroutines – Calling co_await on a coroutine that hasn’t finished may lead to unexpected suspension behavior; ensure proper synchronization.

7. Conclusion

C++20’s coroutine feature opens a new paradigm for writing asynchronous and lazy code in a natural, readable way. By mastering the promise/awaitable model and understanding how the compiler transforms coroutines, developers can build efficient, maintainable systems that leverage the power of modern C++. Happy coroutine coding!

发表评论