はじめに
C++の通常の関数は、一度呼び出されたら、returnするまで処理を中断できません。しかし、非同期処理や、データを少しずつ生成するジェネレータのような機能では、「処理の途中で一旦中断し、呼び出し元に制御を戻し、後でまた続きから再開する」という仕組みが必要になります。
これを実現するのが、C++20で導入されたコルーチンです。コルーチンは、co_await
, co_yield
, co_return
という3つの新しいキーワードを使って、関数の実行を自在に中断・再開できます。
この記事では、コルーチンの基本的な仕組みと、簡単なジェネレータ(数値を順番に生成する関数)を例にした実装方法を解説します。
【前提】C++20とは?
C++20は、2020年に正式化されたC++言語のメジャーアップデート版です。コルーチンはこのC++20で導入された言語機能のため、利用するにはC++20に対応した最新のコンパイラと、適切なコンパイラ設定が必要になります。
コルーチンの仕組みの概要
コルーチンを実装するには、コンパイラとの間で「お約束」を定義する必要があります。その中心となるのがプロミスオブジェクト (promise_type
) です。 コルーチンが返す型(戻り値型)は、自身の内部に promise_type
という名前のクラス(または構造体)を定義しなければなりません。この promise_type
が、コルーチンの状態管理や、値の受け渡し方法などを定義します。
コルーチンを使ったサンプルコード
このコードは、1
から3
までの数値を一つずつ生成する、ジェネレータとして機能するコルーチンcounter
を実装します。
完成コード
#include <iostream>
#include <coroutine> // C++20 コルーチンライブラリ
using namespace std;
// 1. コルーチンの戻り値となる型を定義
struct Generator {
// 内部クラスとして promise_type を定義
struct promise_type {
int current_value; // 現在の値を保持
Generator get_return_object() { return Generator{coroutine_handle<promise_type>::from_promise(*this)}; }
suspend_always initial_suspend() noexcept { return {}; }
suspend_always final_suspend() noexcept { return {}; }
void unhandled_exception() { std::terminate(); }
// co_yieldで値を受け取る
suspend_always yield_value(int value) {
current_value = value;
return {};
}
// co_return; (値なし) のための関数
void return_void() {}
};
coroutine_handle<promise_type> coro_handle;
// 外部からコルーチンを操作するためのメンバ関数
bool resume() {
if (!coro_handle || coro_handle.done()) {
return false;
}
coro_handle.resume();
return !coro_handle.done();
}
int getValue() {
return coro_handle.promise().current_value;
}
~Generator() {
if (coro_handle) {
coro_handle.destroy();
}
}
};
// 2. ジェネレータとして機能するコルーチン
Generator counter() {
cout << "コルーチン: 1を生成します。" << endl;
co_yield 1;
cout << "コルーチン: 2を生成します。" << endl;
co_yield 2;
cout << "コルーチン: 3を生成します。" << endl;
co_yield 3;
cout << "コルーチン: 終了します。" << endl;
}
int main() {
// 3. コルーチンを呼び出し、オブジェクトを取得
auto gen = counter();
// 4. ループで値を一つずつ取り出す
while (gen.resume()) {
cout << "main: 値 " << gen.getValue() << " を取得しました。" << endl;
}
return 0;
}
コードの解説
1. 戻り値型 (Generator
) と promise_type
Generator
は、コルーチンの状態を管理するためのハンドルcoroutine_handle
を保持します。その内部クラスpromise_type
が、コルーチンの実際の動作を定義します。
initial_suspend
: コルーチンが開始した直後に、まず中断するかどうかを決めます。suspend_always
は「常に中断する」という意味です。final_suspend
: コルーチンが終了した後に、中断するかどうかを決めます。yield_value(int value)
:co_yield
が使われたときに呼び出され、生成された値 (value
) を受け取ります。
2. コルーチン本体 (counter
) と co_yield
counter
関数は、戻り値の型がpromise_type
を持つGenerator
であるため、コルーチンとして扱われます。
co_yield 1;
: このキーワードがコルーチンの核心です。- 値
1
を、promise_type
のyield_value
メソッドに渡します。 - 関数の実行をその場で中断し、呼び出し元(
main
関数)に制御を返します。
- 値
3. コルーチンの呼び出し
auto gen = counter();
main
関数でコルーチンcounter
を呼び出すと、コルーチンの本体はすぐには実行されません。initial_suspend
の定義により、即座に中断され、コルーチンを管理するためのGenerator
オブジェクトが返されます。
4. 処理の再開と値の取得
while (gen.resume()) { ... }
gen.resume()
: 中断されているコルーチンの実行を、中断された次の行から再開させます。- コルーチンは次の
co_yield
に到達すると、再び中断します。 gen.getValue()
:promise_type
に保持されている、co_yield
で生成された現在の値を取得します。gen.resume()
は、コルーチンが完全に終了(final_suspend
に到達)するとfalse
を返すため、while
ループが終了します。
まとめ
今回は、C++20のコルーチンの非常に基本的な仕組みを解説しました。
- コルーチンは、
co_await
,co_yield
,co_return
を使って、処理を中断・再開できる特殊な関数。 - コルーチンの動作は、戻り値の型が持つ
promise_type
によって定義される。 co_yield
は、値を生成(yield)して、処理を中断する。- 呼び出し元は、
coroutine_handle
を通じて、コルーチンの処理を再開させることができる。
コルーチンは非同期I/Oやジェネレータなど、複雑な制御フローを、同期処理のようなフラットで読みやすいコードで記述するための、強力な現代C++の機能です。