Generator 예제와 구현 원리

C++20에서는 코루틴(Coroutine) 기능이 표준에 포함되어 비동기 로직이나 상태를 유지하는 반복 처리를 더욱 직관적으로 표현할 수 있게 되었다. 이 문서에서는 Generator 스타일의 코루틴을 예제로 설명하며, promise_type, co_await, std::coroutine_handle의 개념과 구현 방식을 정리한다.

기본 예제

#include <iostream>
#include <coroutine>
using namespace std;

struct Generator {
    struct Promise {
        int value;

        Generator get_return_object() {
            return Generator{ std::coroutine_handle<Promise>::from_promise(*this) };
        }
        auto initial_suspend() { return std::suspend_always{}; }
        auto yield_value(int x) {
            value = x;
            return std::suspend_always{};
        }
        auto return_void() { return std::suspend_never{}; }
        auto final_suspend() noexcept { return std::suspend_always{}; }
        void unhandled_exception() { std::exit(1); }
    };

    using promise_type = Promise;
    std::coroutine_handle<Promise> coro;

    Generator(std::coroutine_handle<Promise> h) : coro(h) {}
    ~Generator() { if (coro) coro.destroy(); }

    int value() const { return coro.promise().value; }
    bool next() { coro.resume(); return !coro.done(); }
};

Generator foo(int n) {
    std::cout << "\tRun 1 : " << std::endl;
    co_await std::suspend_always{}; // suspend after Run 1

    std::cout << "\tRun 2" << std::endl;
    co_await std::suspend_always{};

    std::cout << "\tRun 3" << std::endl;
}

int main() {
    Generator f = foo(10);
    std::cout << "main 1 : " << std::endl;
    f.next();

    std::cout << "main 2" << std::endl;
    f.next();

    std::cout << "main 3" << std::endl;
    f.next();
}

Coroutine 구현 요점

1. co_await std::suspend_always{}

  • 항상 suspend 되므로 caller로 제어권을 넘김
  • 일종의 Awaitable 구조체

2. 반환 타입 규칙 (Generator)

C++의 코루틴 함수는 특정한 반환 타입 규칙을 따라야 한다. 아래 항목이 반드시 구현되어야 한다.

  • 내부에 promise_type이 정의되어 있어야 함

  • std::coroutine_handle<promise_type>를 멤버로 가져야 함

  • 단일 인자를 받는 생성자

  • 소멸자에서 coroutine 파괴 수행

  • promise_type 내부에 다음 5개 필수 메서드:

    • get_return_object()
    • initial_suspend()
    • final_suspend()
    • return_void() 또는 return_value()
    • unhandled_exception()

최소 구현 예시

#include <iostream>
#include <coroutine>

class Generator {
public:
    struct Promise {
        Generator get_return_object() {
            return Generator{ std::coroutine_handle<Promise>::from_promise(*this) };
        }
        auto initial_suspend() { return std::suspend_always{}; }
        auto return_void() { return std::suspend_never{}; }
        auto final_suspend() noexcept { return std::suspend_always{}; }
        void unhandled_exception() { std::exit(1); }
    };

    using promise_type = Promise;
    std::coroutine_handle<Promise> coro;

    Generator(std::coroutine_handle<Promise> c) : coro(c) {}
    ~Generator() { if (coro) coro.destroy(); }
};

항목설명
co_await std::suspend_always{}무조건 suspend. 제어권을 caller에게 넘김
promise_type코루틴 동작을 관리하는 핵심 클래스
coroutine_handle실행 상태를 제어할 수 있는 핸들
yield_value()값을 반환하고 suspend
return_void()반환 없이 종료
final_suspend()마지막 suspend 시점에서 caller로 복귀