コンテンツにスキップ

C++/標準ライブラリ/coroutine

出典: フリー教科書『ウィキブックス(Wikibooks)』

C++教科書 標準ライブラリ編 <coroutine> ヘッダー

[編集]

はじめに

[編集]

C++20 に導入された <coroutine> ヘッダーは、軽量な並行処理と協調型マルチタスクを実現する強力な機能を提供します。従来のスレッドとは異なり、コルーチンは明示的にスケジュールされる必要がなく、コード内でシームレスに中断と再開を制御できます。この章では、<coroutine> ヘッダーの主要な機能と使用方法を詳細に説明します。

コアコンセプト

[編集]

コルーチン特性 (coroutine_traits)

[編集]

coroutine_traits テンプレートクラスは、コルーチン関数の戻り値型であるプロミス型を推論するために使用されます。プロミス型は、コルーチンが非同期に生成する値を格納するオブジェクトです。coroutine_traits は、コンパイラが正しいプロミス型を特定するのに役立ち、コルーチン関数の型推論を可能にします。

template <class R, class... ArgTypes>
struct coroutine_traits;
例:
#include <coroutine>

using namespace std;

template <typename T>
struct MyPromise {
    T get_return_value();
    void suspend_coroutine();
    void resume_coroutine();
};

template <typename T>
coroutine_handle<MyPromise<T>> my_coroutine(T value) {
    // ...
}

この例では、MyPromise<T>my_coroutine コルーチン関数のプロミス型であることを coroutine_traits が推論します。

コルーチンハンドル (coroutine_handle)

[編集]

coroutine_handle テンプレートクラスは、中断または実行中のコルーチンを参照するために使用されます。コルーチンハンドルは、コルーチンの状態、メモリ アドレス、および関連するプロミスオブジェクトへのアクセスを提供します。

template <class Promise = void>
struct coroutine_handle;
コルーチンハンドルを作成するには、以下のいずれかの方法を使用できます。
コルーチン関数を呼び出す
coroutine_handle<MyPromise<T>> h = my_coroutine(value);
nullptr_t から構築
coroutine_handle<MyPromise<T>> h = nullptr;
プロミスオブジェクトから構築
MyPromise<T> p;
coroutine_handle<MyPromise<T>> h = p;
コルーチンハンドルを使用して、コルーチンの状態を確認したり、操作したりすることができます。
operator bool()
コルーチンが有効かどうかを確認します。
done()
コルーチンが完了したかどうかを確認します。
operator()()
コルーチンを再開します。
resume()
コルーチンを再開します。
destroy()
コルーチンを破棄します。
address()
コルーチンのメモリ アドレスを取得します。
from_address(void* addr)
メモリ アドレスからコルーチンハンドルを作成します。

ノープコルーチン

[編集]

noop_coroutine_promisenoop_coroutine_handle は、副作用のないコルーチンを表現するために使用されます。これらのコルーチンは、値を生成したり、外部状態を変更したりしません。

struct noop_coroutine_promise {};

template <>
struct coroutine_handle<noop_coroutine_promise> {
    // ...
};

noop_coroutine 関数は、副作用のないコルーチンハンドルを作成します。

coroutine_handle<noop_coroutine_promise> noop_coroutine() noexcept;

自明な待機可能オブジェクト

[編集]

suspend_neversuspend_always は、コルーチンの待機動作を制御するために使用されます。

suspend_never
コルーチンが決して中断しないことを示します。
suspend_always
コルーチンが常に中断することを示します。
struct suspend_never {
    constexpr bool await_ready() const noexcept { return true; }
    constexpr void await_suspend(coroutine_handle<>) const noexcept {}
    constexpr void await_resume() const noexcept {}
};

struct suspend_always {
    constexpr bool await_ready() const noexcept { return false; }
    constexpr void await_suspend(coroutine_handle<>) const noexcept {}
    constexpr void await_resume() const noexcept {}
};

これらのオブジェクトは、await 式で使用できます。

<coroutine> ヘッダーの使用

[編集]

インクルードディレクティブ

[編集]

<coroutine> ヘッダーを使用するには、以下のインクルードディレクティブを使用する必要があります。

#include <coroutine>

名前空間 std

[編集]

<coroutine> ヘッダー内のすべての機能は std 名前空間に属します。そのため、これらの機能を使用するには、std 名前空間を明示的に指定する必要があります。

namespace std {
    // ...
}

コルーチンハンドルメンバー関数

[編集]

作成/リセット

[編集]

以下のメンバー関数を使用して、コルーチンハンドルを作成またはリセットできます。

coroutine_handle()
空のコルーチンハンドルを作成します。
nullptr_t コンストラクター
nullptr_t値からコルーチンハンドルを作成します。
代入演算子
コルーチンハンドルを別のコルーチンハンドルに代入します。
coroutine_handle<> h1; // 空のコルーチンハンドルを作成
coroutine_handle<MyPromise<T>> h2 = nullptr; // ヌルポインタからコルーチンハンドルを作成
coroutine_handle<MyPromise<T>> h3 = h2; // コルーチンハンドルを代入

エクスポート/インポート

[編集]

以下のメンバー関数を使用して、コルーチンのメモリ アドレスを取得したり、アドレスからコルーチンハンドルを作成したりできます。

address()
コルーチンのメモリ アドレスを取得します。
from_address(void* addr)
メモリ アドレスからコルーチンハンドルを作成します。
void* addr = h2.address(); // コルーチンのメモリ アドレスを取得
coroutine_handle<MyPromise<T>> h4 = coroutine_handle<MyPromise<T>>::from_address(addr); // メモリ アドレスからコルーチンハンドルを作成

オブザーバー

[編集]

以下のメンバー関数を使用して、コルーチンハンドルが有効かどうか、およびコルーチンが完了したかどうかを確認できます。

operator bool()
コルーチンハンドルが有効かどうかを確認します。
done()
コルーチンが完了したかどうかを確認します。
if (h2) {
    // コルーチンハンドルが有効
    if (h2.done()) {
        // コルーチンが完了
    }
}

再開

[編集]

以下のメンバー関数を使用して、コルーチンを再開、再起動、または破棄できます。

operator()()
コルーチンを再開します。
resume()
コルーチンを再開します。
destroy()
コルーチンを破棄します。
h2(); // コルーチンを再開
h2.resume(); // コルーチンを再開
h2.destroy(); // コルーチンを破棄

プロミスアクセス (プロミス型の場合)

[編集]

以下のメンバー関数を使用して、関連するプロミスオブジェクトにアクセスできます (プロミス型の場合)。

promise()
関連するプロミスオブジェクトを取得します。
MyPromise<T>& p = h2.promise(); // 関連するプロミスオブジェクトを取得

noop_coroutine 関数

[編集]

noop_coroutine 関数は、副作用のないコルーチンハンドルを作成します。

coroutine_handle<noop_coroutine_promise> noop_coroutine() noexcept;

この関数は、副作用のない非同期操作を表すために使用できます。

[編集]

以下の例では、コルーチンを使用して非同期にファイルを読み込む方法を示します。

#include <coroutine>
#include <fstream>
#include <string>

using namespace std;

auto read_file(const string& filename)
    -> coroutine_handle<noop_coroutine_promise> {
    ifstream file(filename);
    if (file.is_open()) {
        string line;
        while (getline(file, line)) {
            // 各行を処理
            // ...
        }
    } else {
        // エラー処理
    }
}

auto main() -> int {
    coroutine_handle<noop_coroutine_promise> const h = read_file("myfile.txt");
    h();  // コルーチンを実行
    return 0;
}

この例では、read_file コルーチンは、指定されたファイルを読み込み、各行を処理します。コルーチンは noop_coroutine_promise を使用するため、副作用はありません。

結論

[編集]

<coroutine> ヘッダーは、C++ に強力な非同期プログラミング機能を提供します。コルーチンは、軽量で柔軟な並行処理メカニズムであり、従来のスレッドよりも多くの利点をもたらします。

利点
軽量性
コルーチンはスレッドよりも軽量で、メモリと CPU リソースを節約できます。
柔軟性
コルーチンはシームレスに中断と再開が可能で、複雑な非同期処理を表現するのに適しています。
明瞭性
コルーチンはコードの流れを明確にし、デバッグと保守を容易にします。
ユースケース
  • 非同期 I/O
  • ネットワークプログラミング
  • イベント駆動プログラミング
  • ゲーム開発
  • 科学計算
今後の展望
C++20 で導入されたコルーチンはまだ初期段階であり、今後さらに発展していくことが期待されます。新しい標準ライブラリ機能や、サードパーティ製のライブラリやフレームワークの登場により、コルーチンの使いやすさと汎用性が向上していくでしょう。

補足

[編集]

この章では、<coroutine> ヘッダーの主要な機能と使用方法について説明しました。

また、コルーチンは比較的新しい機能であり、すべてのコンパイラで完全にサポートされているわけではないことに注意する必要があります。最新のコンパイラを使用していることを確認してください。