Coroutines (C++20)
코루틴은 실행을 일시 중단했다가 나중에 재개할 수 있는 함수입니다. 코루틴은 스택리스(stackless)입니다: 호출자에게 반환하여 실행을 중단하며, 실행을 재개하는 데 필요한 데이터는 스택과 별도로 저장됩니다. 이를 통해 비동기적으로 실행되는 순차적 코드(예: 명시적 콜백 없이 논블로킹 I/O를 처리하기 위해)가 가능하며, 지연 계산되는 무한 시퀀스에 대한 알고리즘과 기타 용도도 지원합니다.
함수는 다음과 같은 요소가 정의에 포함될 경우 코루틴입니다:
- the co_await 표현식 — 재개될 때까지 실행을 일시 중단함
task<> tcp_echo_server() { char data[1024]; while (true) { std::size_t n = co_await socket.async_read_some(buffer(data)); co_await async_write(socket, buffer(data, n)); } }
- the co_yield 표현식 — 값을 반환하며 실행을 일시 중단하기 위한
generator<unsigned int> iota(unsigned int n = 0) { while (true) co_yield n++; }
- co_return 문 — 값을 반환하며 실행을 완료합니다
lazy<int> f() { co_return 7; }
모든 코루틴은 아래에 명시된 여러 요구사항을 충족하는 반환 타입을 가져야 합니다.
목차 |
제한 사항
코루틴은
가변 인수
, 일반
return
문, 또는
자리 표시자 반환 타입
(
auto
또는
Concept
)을 사용할 수 없습니다.
Consteval 함수 , constexpr 함수 , 생성자 , 소멸자 , 그리고 main 함수 는 코루틴이 될 수 없습니다.
실행
각 코루틴은 다음과 연관됩니다
- 프로미스 객체 , 코루틴 내부에서 조작됩니다. 코루틴은 이 객체를 통해 결과나 예외를 제출합니다. 프로미스 객체는 std::promise 와 아무런 관련이 없습니다.
- 코루틴 핸들 , 코루틴 외부에서 조작됩니다. 이는 코루틴 실행을 재개하거나 코루틴 프레임을 파괴하는 데 사용되는 비소유 핸들입니다.
- 코루틴 상태 , 내부적이며 동적으로 할당된 저장 공간(할당이 최적화되지 않는 한)을 포함하는 객체입니다.
-
- promise 객체
- 매개변수들 (모두 값으로 복사됨)
- 현재 중단 지점의 일부 표현으로, resume이 어디서 계속해야 하는지 알고 destroy가 어떤 지역 변수들이 범위 내에 있었는지 알 수 있도록 함
- 현재 중단 지점을 가로지르는 수명을 가진 지역 변수들과 임시 객체들
코루틴이 실행을 시작할 때, 다음과 같은 작업을 수행합니다:
- 코루틴 상태 객체를 operator new 를 사용하여 할당합니다.
- 모든 함수 매개변수를 코루틴 상태에 복사합니다: 값에 의한 전달 매개변수는 이동되거나 복사되며, 참조에 의한 전달 매개변수는 참조로 유지됩니다(따라서 참조된 객체의 수명이 종료된 후 코루틴이 재개되면 댕글링 참조가 될 수 있습니다 — 예제는 아래 참조).
- promise 객체의 생성자를 호출합니다. promise 타입이 모든 코루틴 매개변수를 취하는 생성자를 가지고 있는 경우, 해당 생성자가 사후 복사된 코루틴 인자들과 함께 호출됩니다. 그렇지 않으면 기본 생성자가 호출됩니다.
- promise. get_return_object ( ) 를 호출하고 결과를 지역 변수에 보관합니다. 이 호출의 결과는 코루틴이 처음 일시 중단될 때 호출자에게 반환됩니다. 이 단계까지 발생하는 모든 예외는 promise에 배치되지 않고 호출자에게 전파됩니다.
-
promise.
initial_suspend
(
)
를 호출하고 그 결과를
co_await합니다. 일반적인Promise타입은 지연 시작 코루틴의 경우 std::suspend_always 를 반환하거나, 즉시 시작 코루틴의 경우 std::suspend_never 를 반환합니다. - co_await promise. initial_suspend ( ) 가 재개될 때, 코루틴의 본문 실행을 시작합니다.
매개변수가 댕글링(dangling)이 되는 몇 가지 예시:
#include <coroutine> #include <iostream> struct promise; struct coroutine : std::coroutine_handle<promise> { using promise_type = ::promise; }; struct promise { coroutine get_return_object() { return {coroutine::from_promise(*this)}; } std::suspend_always initial_suspend() noexcept { return {}; } std::suspend_always final_suspend() noexcept { return {}; } void return_void() {} void unhandled_exception() {} }; struct S { int i; coroutine f() { std::cout << i; co_return; } }; void bad1() { coroutine h = S{0}.f(); // S{0} 소멸됨 h.resume(); // 재개된 코루틴이 std::cout << i를 실행하며, 해제된 후의 S::i를 사용함 h.destroy(); } coroutine bad2() { S s{0}; return s.f(); // 반환된 코루틴은 use after free를 발생시키지 않고는 재개할 수 없음 } void bad3() { coroutine h = [i = 0]() -> coroutine // 코루틴이기도 한 람다 { std::cout << i; co_return; }(); // 즉시 호출됨 // 람다 소멸됨 h.resume(); // 해제된 후의 (익명 람다 타입)::i를 사용함 h.destroy(); } void good() { coroutine h = [](int i) -> coroutine // i를 코루틴 매개변수로 만듦 { std::cout << i; co_return; }(0); // 람다 소멸됨 h.resume(); // 문제 없음, i가 값에 의한 매개변수로 코루틴 프레임에 복사됨 // 프레임에 값에 의한 매개변수로 복사됨 h.destroy(); }
코루틴이 일시 중단 지점에 도달했을 때
- 이전에 획득한 반환 객체는, 필요한 경우 코루틴의 반환 타입으로 암시적 변환된 후 호출자/재개자에게 반환됩니다.
코루틴이 co_return 문에 도달하면 다음을 수행합니다:
- promise. return_void ( ) 를 호출합니다
-
- co_return ;
- co_return expr ; 여기서 expr 의 타입은 void 입니다
- 또는 promise. return_value ( expr ) 를 호출합니다 - co_return expr ; 의 경우, 여기서 expr 이 void가 아닌 타입을 가질 때
- 자동 저장 기간을 가진 모든 변수들을 생성된 역순으로 파괴합니다
- promise. final_suspend ( ) 를 호출하고 그 결과를 co_await 합니다
코루틴의 끝까지 도달하는 것은
co_return
;
와 동일하지만,
Promise
범위 내에서
return_void
선언을 찾을 수 없는 경우 동작은 정의되지 않습니다. 함수 본문에 정의 키워드가 하나도 없는 함수는 반환 타입과 관계없이 코루틴이 아니며, 끝까지 도달했을 때 반환 타입이 (cv 한정자가 있을 수 있는)
void
가 아닌 경우 동작은 정의되지 않습니다.
// task가 어떤 코루틴 task 타입이라고 가정 task<void> f() { // 코루틴이 아니므로 정의되지 않은 동작 } task<void> g() { co_return; // OK } task<void> h() { co_await g(); // OK, 암시적 co_return; }
코루틴이 잡히지 않은 예외로 종료되면 다음을 수행합니다:
- 예외를 포착하고 catch 블록 내에서 promise. unhandled_exception ( ) 를 호출합니다
- promise. final_suspend ( ) 를 호출하고 결과를 co_await 합니다 (예: 연속 실행을 재개하거나 결과를 게시하기 위해). 이 시점에서 코루틴을 재개하는 것은 정의되지 않은 동작입니다.
코루틴 상태가 파괴될 때, 즉 co_return 이나 포착되지 않은 예외로 인해 종료되었거나, 핸들을 통해 파괴된 경우 다음과 같은 작업을 수행합니다:
- promise 객체의 소멸자를 호출합니다.
- 함수 매개변수 복사본들의 소멸자들을 호출합니다.
- 코루틴 상태에 사용된 메모리를 해제하기 위해 operator delete 를 호출합니다.
- 실행을 호출자/재개자에게 되돌립니다.
동적 할당
코루틴 상태는 비배열 operator new 를 통해 동적으로 할당됩니다.
만약
Promise
타입이 클래스 수준의 대체를 정의하면, 그것이 사용될 것이며, 그렇지 않으면 전역
operator new
가 사용될 것입니다.
만약
Promise
타입이 추가 매개변수를 취하는
operator new
의 배치 형태를 정의하고, 첫 번째 인자가 요청된 크기(타입
std::size_t
)이며 나머지가 코루틴 함수 인자와 일치하는 인자 목록과 매치된다면, 해당 인자들은
operator new
에 전달됩니다 (이를 통해 코루틴에 대해
선행-할당자-규약
을 사용할 수 있습니다).
operator new 호출은 다음과 같은 경우 최적화되어 제거될 수 있습니다(사용자 정의 할당자를 사용하는 경우에도).
- 코루틴 상태의 수명은 호출자의 수명 내에서 엄격하게 중첩되며,
- 코루틴 프레임의 크기는 호출 지점에서 알려져 있습니다.
이 경우, 코루틴 상태는 호출자의 스택 프레임(호출자가 일반 함수인 경우) 또는 코루틴 상태(호출자가 코루틴인 경우)에 내장됩니다.
할당이 실패하면 코루틴은
std::bad_alloc
을(를) 던집니다. 단,
Promise
타입이 멤버 함수
Promise
::
get_return_object_on_allocation_failure
(
)
을 정의한 경우는 예외입니다. 해당 멤버 함수가 정의된 경우, 할당은
operator new
의 nothrow 형식을 사용하며, 할당 실패 시 코루틴은 즉시
Promise
::
get_return_object_on_allocation_failure
(
)
에서 얻은 객체를 호출자에게 반환합니다. 예를 들어:
struct Coroutine::promise_type { /* ... */ // 비-던지기 operator-new 사용 보장 static Coroutine get_return_object_on_allocation_failure() { std::cerr << __func__ << '\n'; throw std::bad_alloc(); // 또는, return Coroutine(nullptr); } // 사용자 정의 비-던지기 new 오버로드 void* operator new(std::size_t n) noexcept { if (void* mem = std::malloc(n)) return mem; return nullptr; // 할당 실패 } };
Promise
Promise
타입은 컴파일러가 코루틴의 반환 타입으로부터
std::coroutine_traits
를 사용하여 결정합니다.
공식적으로, 다음과 같이 정의합니다.
-
R와Args...는 각각 코루틴의 반환 타입과 매개변수 타입 목록을 나타냅니다, -
ClassT는 비정적 멤버 함수로 정의된 경우 코루틴이 속한 클래스 타입을 나타냅니다, - cv 는 비정적 멤버 함수로 정의된 경우 함수 선언 에서 선언된 cv 한정자를 나타냅니다,
그
Promise
유형은 다음과 같이 결정됩니다:
- std:: coroutine_traits < R, Args... > :: promise_type , 코루틴이 암시적 객체 멤버 함수 로 정의되지 않은 경우,
-
std::
coroutine_traits
<
R,
cvClassT & , Args... > :: promise_type , 코루틴이 우측값 참조 한정자가 없는 암시적 객체 멤버 함수로 정의된 경우, -
std::
coroutine_traits
<
R,
cvClassT && , Args... > :: promise_type , 코루틴이 우측값 참조 한정된 암시적 객체 멤버 함수로 정의된 경우.
예를 들어:
| 코루틴이 다음과 같이 정의된 경우 ... |
해당
Promise
타입은 ...
|
|---|---|
| task < void > foo ( int x ) ; | std:: coroutine_traits < task < void > , int > :: promise_type |
| task < void > Bar :: foo ( int x ) const ; | std:: coroutine_traits < task < void > , const Bar & , int > :: promise_type |
| task < void > Bar :: foo ( int x ) && ; | std:: coroutine_traits < task < void > , Bar && , int > :: promise_type |
co_await
단항 연산자 co_await 는 코루틴을 일시 중단하고 호출자에게 제어권을 반환합니다.
co_await
expr
|
|||||||||
co_await 표현식은 일반 potentially-evaluated 표현식 내에서만 정규 함수 본문 (람다 표현식의 함수 본문을 포함하여) 안에 나타날 수 있으며, 다음 위치에서는 나타날 수 없습니다
- 핸들러 내에서,
- 선언 문에서, 단 해당 선언 문의 초기화 부분에 나타나는 경우를 제외하고,
-
단순 선언
의
init-statement
에서 (
if,switch,for및 [[../range- for |range- for ]] 참조), 단 해당 init-statement 의 초기화 부분에 나타나는 경우를 제외하고, - 기본 인자 에서, 또는
- 정적 또는 스레드 저장 기간 을 가진 블록 범위 변수의 초기화 부분에서.
|
co_await 표현식은 잠재적으로 평가되는 하위 표현식으로 계약 어설션 의 조건식에 사용될 수 없습니다. |
(since C++26) |
먼저, expr 은 다음과 같이 awaitable로 변환됩니다:
- expr 이 초기 일시 중단 지점, 최종 일시 중단 지점 또는 yield 표현식에 의해 생성된 경우, awaitable은 원본 그대로의 expr 입니다.
-
그렇지 않고 현재 코루틴의
Promise타입에await_transform멤버 함수가 있는 경우, awaitable은 promise. await_transform ( expr ) 입니다. - 그렇지 않은 경우, awaitable은 원본 그대로의 expr 입니다.
그런 다음, 다음과 같이 awaiter 객체를 획득합니다:
- operator co_await 에 대한 오버로드 해상도가 단일 최적 오버로드를 제공하는 경우, awaiter는 해당 호출의 결과입니다:
-
- awaitable. operator co_await ( ) 멤버 오버로드의 경우,
- operator co_await ( static_cast < Awaitable && > ( awaitable ) ) 비멤버 오버로드의 경우.
- 그렇지 않고, 오버로드 해결이 operator co_await 를 찾지 못하면, awaiter는 있는 그대로 awaitable입니다.
- 그렇지 않고, 오버로드 해결이 모호하면 프로그램은 잘못된 형식입니다.
위의 표현식이 prvalue 인 경우, awaiter 객체는 그것으로부터 glvalue 인 경우, awaiter 객체는 그것이 참조하는 객체입니다.
그런 다음, awaiter. await_ready ( ) 가 호출됩니다 (이는 결과가 준비되었거나 동기적으로 완료될 수 있다고 알려진 경우 일시 중단 비용을 피하기 위한 단축 경로입니다). 부울 값으로 문맥적으로 변환된 결과가 false 인 경우,
- 코루틴이 일시 중단됩니다 (해당 코루틴 상태는 지역 변수와 현재 중단 지점으로 채워집니다).
-
awaiter.
await_suspend
(
handle
)
가 호출됩니다. 여기서 handle은 현재 코루틴을 나타내는 코루틴 핸들입니다. 이 함수 내에서 일시 중단된 코루틴 상태는 해당 핸들을 통해 관찰 가능하며, 이 함수의 책임은 이를 어떤 실행기에서 재개하도록 스케줄링하거나 파괴하는 것입니다 (false를 반환하는 것도 스케줄링으로 간주됩니다).
-
만약
await_suspend이 void 를 반환하면, 제어는 현재 코루틴의 호출자/재개자에게 즉시 반환됩니다 (이 코루틴은 계속 일시 중단된 상태로 유지됩니다). 그렇지 않으면 -
만약
await_suspend이 bool 를 반환하면,
-
- 값 true 는 현재 코루틴의 호출자/재개자에게 제어를 반환합니다.
- 값 false 는 현재 코루틴을 재개합니다.
-
만약
await_suspend이 다른 코루틴의 코루틴 핸들을 반환하면, 해당 핸들이 재개됩니다 ( handle. resume ( ) 호출을 통해) (이는 결국 현재 코루틴이 재개되도록 연쇄적으로 발생할 수 있습니다). -
만약
await_suspend이 예외를 던지면, 예외가 포착되고 코루틴이 재개된 후 예외가 즉시 다시 던져집니다.
-
만약
마지막으로, awaiter. await_resume ( ) 가 호출되고 (코루틴이 일시 중단되었는지 여부와 관계없이), 그 결과가 전체 co_await expr 표현식의 결과가 됩니다.
코루틴이 co_await 표현식에서 일시 중단된 후, 나중에 재개되는 경우, 재개 지점은 awaiter. await_resume ( ) 호출 바로 전입니다.
코루틴이 awaiter. await_suspend ( ) 에 진입하기 전에 완전히 일시 중단된다는 점에 유의하십시오. 해당 핸들은 다른 스레드와 공유될 수 있으며 await_suspend ( ) 함수가 반환되기 전에 재개될 수 있습니다. (기본 메모리 안전성 규칙이 여전히 적용되므로, 락 없이 코루틴 핸들이 여러 스레드 간에 공유되는 경우 awaiter는 최소한 release semantics 를 사용해야 하며, 재개 측에서는 최소한 acquire semantics 를 사용해야 합니다.) 예를 들어, 코루틴 핸들을 콜백 내에 배치하여 비동기 I/O 작업이 완료될 때 스레드 풀에서 실행되도록 예약할 수 있습니다. 이 경우 현재 코루틴이 이미 재개되어 awaiter 객체의 소멸자가 실행되었을 수 있으므로, await_suspend ( ) 가 현재 스레드에서 실행을 계속하는 동안 모든 작업이 동시에 발생할 수 있습니다. 따라서 await_suspend ( ) 는 * this 가 소멸된 것으로 간주하고, 핸들이 다른 스레드에 공개된 후에는 이에 접근하지 않아야 합니다.
예제
#include <coroutine> #include <iostream> #include <stdexcept> #include <thread> auto switch_to_new_thread(std::jthread& out) { struct awaitable { std::jthread* p_out; bool await_ready() { return false; } void await_suspend(std::coroutine_handle<> h) { std::jthread& out = *p_out; if (out.joinable()) throw std::runtime_error("Output jthread parameter not empty"); out = std::jthread([h] { h.resume(); }); // Potential undefined behavior: accessing potentially destroyed *this // std::cout << "New thread ID: " << p_out->get_id() << '\n'; std::cout << "New thread ID: " << out.get_id() << '\n'; // this is OK } void await_resume() {} }; return awaitable{&out}; } struct task { struct promise_type { task get_return_object() { return {}; } std::suspend_never initial_suspend() { return {}; } std::suspend_never final_suspend() noexcept { return {}; } void return_void() {} void unhandled_exception() {} }; }; task resuming_on_new_thread(std::jthread& out) { std::cout << "Coroutine started on thread: " << std::this_thread::get_id() << '\n'; co_await switch_to_new_thread(out); // awaiter destroyed here std::cout << "Coroutine resumed on thread: " << std::this_thread::get_id() << '\n'; } int main() { std::jthread out; resuming_on_new_thread(out); }
가능한 출력:
Coroutine started on thread: 139972277602112 New thread ID: 139972267284224 Coroutine resumed on thread: 139972267284224
참고: awaiter 객체는 코루틴 상태의 일부이며(생명주기가 일시 중단 지점을 가로지르는 임시 객체로서), co_await 표현식이 완료되기 전에 파괴됩니다. 이는 추가적인 동적 할당 없이 일부 비동기 I/O API에서 요구하는 작업별 상태를 유지하는 데 사용될 수 있습니다.
표준 라이브러리는 두 가지 간단한 awaitable을 정의합니다: std::suspend_always 와 std::suspend_never .
|
이 섹션은 불완전합니다
이유: 예시 |
| promise_type :: await_transform 및 프로그램 제공 어웨이터 데모 |
|---|
예제
이 코드 실행하기
#include <cassert> #include <coroutine> #include <iostream> struct tunable_coro { // 생성자 매개변수를 통해 "준비 상태"가 결정되는 awaiter. class tunable_awaiter { bool ready_; public: explicit(false) tunable_awaiter(bool ready) : ready_{ready} {} // 세 가지 표준 awaiter 인터페이스 함수: bool await_ready() const noexcept { return ready_; } static void await_suspend(std::coroutine_handle<>) noexcept {} static void await_resume() noexcept {} }; struct promise_type { using coro_handle = std::coroutine_handle<promise_type>; auto get_return_object() { return coro_handle::from_promise(*this); } static auto initial_suspend() { return std::suspend_always(); } static auto final_suspend() noexcept { return std::suspend_always(); } static void return_void() {} static void unhandled_exception() { std::terminate(); } // 사용자가 제공하는 커스텀 awaiter를 반환하는 변환 함수: auto await_transform(std::suspend_always) { return tunable_awaiter(!ready_); } void disable_suspension() { ready_ = false; } private: bool ready_{true}; }; tunable_coro(promise_type::coro_handle h) : handle_(h) { assert(h); } // For simplicity, declare these 4 special functions as deleted: tunable_coro(tunable_coro const&) = delete; tunable_coro(tunable_coro&&) = delete; tunable_coro& operator=(tunable_coro const&) = delete; tunable_coro& operator=(tunable_coro&&) = delete; ~tunable_coro() { if (handle_) handle_.destroy(); } void disable_suspension() const { if (handle_.완료()) return; handle_.promise().disable_suspension(); handle_(); } bool operator()() { if (!handle_.완료()) handle_(); return !handle_.완료(); } private: promise_type::coro_handle handle_; }; tunable_coro generate(int n) { for (int i{}; i != n; ++i) { std::cout << i << ' '; // co_await에 전달된 awaiter는 promise_type::await_transform으로 전달되며 // 초기에는 일시 중단을 유발하는 tunable_awaiter 문제(호출자에게 반환) // 각 반복에서 main), 그러나 disable_suspension 호출 후에는 일시 중단 없음 // 발생하고 루프가 main()으로 돌아가지 않고 끝까지 실행됩니다. co_await std::suspend_always{}; } } int main() { auto coro = generate(8); coro(); // 첫 번째 요소가 0인 경우에만 출력 for (int k{}; k < 4; ++k) { coro(); // 1 2 3 4를 각 반복마다 하나씩 출력함 std::cout << ": "; } coro.disable_suspension(); coro(); // 5 6 7 테일 번호를 한 번에 출력합니다 } 출력: 0 1 : 2 : 3 : 4 : 5 6 7 |
co_yield
co_yield
표현식은 호출자에게 값을 반환하고 현재 코루틴을 일시 중단합니다: 이것은 재개 가능한 제너레이터 함수의 공통 구성 요소입니다.
co_yield
expr
|
|||||||||
co_yield
braced-init-list
|
|||||||||
다음과 동일합니다
co_await promise.yield_value(expr)
일반적인 제너레이터의
yield_value
는 인자를 제너레이터 객체에 저장(복사/이동하거나 인자의 수명이
co_await
내의 중단 지점을 가로지르므로 주소만 저장)하고
std::suspend_always
를 반환하여 호출자/재개자에게 제어권을 이전합니다.
#include <coroutine> #include <cstdint> #include <exception> #include <iostream> template<typename T> struct Generator { // 클래스 이름 'Generator'는 우리의 선택이며 코루틴에 필수적인 것은 아닙니다 // 마법. 컴파일러는 'co_yield' 키워드의 존재로 코루틴을 인식합니다. // 'MyGenerator' (또는 다른 이름)을 사용할 수 있습니다. 단, 포함하기만 하면 됩니다 // 'MyGenerator get_return_object()' 메서드를 가진 중첩 구조체 promise_type. // (참고: 생성자와 소멸자의 선언을 조정하는 것이 필요합니다 // (리네이밍 시 주의하십시오.) struct promise_type; using handle_type = std::coroutine_handle<promise_type>; struct promise_type // required { T value_; std::exception_ptr exception_; Generator get_return_object() { return Generator(handle_type::from_promise(*this)); } std::suspend_always initial_suspend() { return {}; } std::suspend_always final_suspend() noexcept { return {}; } void unhandled_exception() { exception_ = std::current_exception(); } // 저장 // 예외 template<std::convertible_to<T> From> // C++20 concept std::suspend_always yield_value(From&& from) { value_ = std::forward<From>(from); // 결과를 promise에 캐싱 return {}; } void return_void() {} }; handle_type h_; Generator(handle_type h) : h_(h) {} ~Generator() { h_.destroy(); } explicit operator bool() { fill(); // 코루틴이 완료되었는지 여부를 안정적으로 알아내는 유일한 방법은, // 다음에 생성될 값이 있는지 여부(co_yield) // 코루틴에서 C++ getter(아래 operator ())를 통한 접근은 실행/재개를 의미합니다 // 다음 co_yield 지점까지 코루틴을 실행합니다(또는 끝까지 실행되도록 합니다). // 그런 다음 결과를 promise에 저장/캐시하여 getter(아래 operator())가 접근할 수 있도록 합니다 // 코루틴을 실행하지 않고 가져오기 위해 return !h_.완료(); } T operator()() { fill(); full_ = false; // 이전에 캐시된 내용을 이동합니다 // 프라미스를 다시 비우기 위한 결과 return std::move(h_.promise().value_); } private: bool full_ = false; void fill() { if (!full_) { h_(); if (h_.promise().exception_) std::rethrow_exception(h_.promise().exception_); // 호출된 컨텍스트에서 코루틴 예외 전파 full_ = true; } } }; Generator<std::uint64_t> fibonacci_sequence(unsigned n) { if (n == 0) co_return; if (n > 94) throw std::runtime_error("피보나치 수열이 너무 큽니다. 요소들이 오버플로우될 것입니다."); co_yield 0; if (n == 1) co_return; co_yield 1; if (n == 2) co_return; std::uint64_t a = 0; std::uint64_t b = 1; for (unsigned i = 2; i < n; ++i) { std::uint64_t s = a + b; co_yield s; a = b; b = s; } } int main() { try { auto gen = fibonacci_sequence(10); // uint64_t 오버플로우 전 최대 94 for (int j = 0; gen; ++j) std::cout << "fib(" << j << ")=" << gen() << '\n'; } catch (const std::exception& ex) { std::cerr << "Exception: " << ex.무엇() << '\n'; } catch (...) { std::cerr << "알 수 없는 예외.\n"; } }
출력:
fib(0)=0 fib(1)=1 fib(2)=1 fib(3)=2 fib(4)=3 fib(5)=5 fib(6)=8 fib(7)=13 fib(8)=21 fib(9)=34
참고 사항
| 기능 테스트 매크로 | 값 | 표준 | 기능 |
|---|---|---|---|
__cpp_impl_coroutine
|
201902L
|
(C++20) | 코루틴 (컴파일러 지원) |
__cpp_lib_coroutine
|
201902L
|
(C++20) | 코루틴 (라이브러리 지원) |
__cpp_lib_generator
|
202207L
|
(C++23) | std::generator : 범위를 위한 동기식 코루틴 생성기 |
키워드
co_await , co_return , co_yield
라이브러리 지원
코루틴 지원 라이브러리 는 코루틴을 위한 컴파일 타임 및 런타임 지원을 제공하는 여러 타입을 정의합니다.
결함 보고서
다음 동작 변경 결함 보고서는 이전에 발표된 C++ 표준에 소급 적용되었습니다.
| DR | 적용 대상 | 게시된 동작 | 올바른 동작 |
|---|---|---|---|
| CWG 2556 | C++20 |
잘못된
return_void
가 코루틴 끝에 도달하는
동작을 정의되지 않음으로 만듦 |
이 경우 프로그램이
잘못된 형식임 |
| CWG 2668 | C++20 |
co_await
가 람다 표현식에
나타날 수 없었음 |
허용됨 |
| CWG 2754 | C++23 |
*
this
가 명시적 객체 멤버 함수에 대한
promise 객체를 생성할 때 취해짐 |
*
this
는 이 경우
취해지지 않음 |
참고 항목
|
(C++23)
|
동기식
view
코루틴
제너레이터를 나타내는
(클래스 템플릿) |
외부 링크
| 1. | Lewis Baker, 2017-2022 - Asymmetric Transfer. |
| 2. | David Mazières, 2021 - Tutorial on C++20 coroutines. |
| 3. | Chuanqi Xu & Yu Qi & Yao Han, 2021 - C++20 Principles and Applications of Coroutine. (Chinese) |
| 4. | Simon Tatham, 2023 - Writing custom C++20 coroutine systems. |