【C++】std::coroutineで出来ることと典型的な使い方【標準ライブラリ】
std::coroutineとは
C++20で導入されたコルーチンは、関数の実行を途中で一時停止し、後で再開する機能を提供します。従来の関数とは異なり、コルーチンは途中で状態を保存しながら実行を継続できます。これにより、非同期処理、ジェネレータ、協調的マルチタスク処理などが簡潔に記述できます。
コルーチンの基本構造
C++のコルーチンを使うには、以下の3つの要素が必要です。
- Promise Type: コルーチンの状態管理を行うクラス
- ハンドル(std::coroutine_handle): コルーチンを制御するオブジェクト
- サスペンドポイント(suspend points): コルーチンを一時停止・再開する箇所
以下のコードは、基本的なコルーチンの構造を示しています。
#include <coroutine>
#include <iostream>
struct SimpleCoroutine {
struct promise_type {
SimpleCoroutine get_return_object() { return {}; }
std::suspend_never initial_suspend() { return {}; }
std::suspend_never final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() {}
};
};
SimpleCoroutine my_coroutine() {
std::cout << "Coroutine started" << std::endl;
co_return;
}
int main() {
my_coroutine();
return 0;
}
Promise Typeとは
Promise Typeは、コルーチンのライフサイクルを管理する重要な要素です。このクラスは、以下の主要なメンバ関数を持ちます。
get_return_object()
: コルーチンが返すオブジェクトを作成initial_suspend()
: コルーチンの開始時にサスペンドするか決定final_suspend()
: コルーチン終了時にサスペンドするか決定return_value(T)
: コルーチンの戻り値をセットunhandled_exception()
: 例外処理
コルーチンの中断点
コルーチンには3つの主な中断点があります。
co_await
: 非同期処理を待機co_yield
: 値を返しながら中断co_return
: 値を返して終了
ジェネレータの実装
ジェネレータは、値を逐次生成するコルーチンの一種です。
#include <coroutine>
#include <iostream>
template<typename T>
struct Generator {
struct promise_type {
T current_value;
Generator get_return_object() { return Generator{this}; }
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
std::suspend_always yield_value(T value) {
current_value = value;
return {};
}
void return_void() {}
void unhandled_exception() { std::terminate(); }
};
std::coroutine_handle<promise_type> handle;
Generator(promise_type* p) : handle(std::coroutine_handle<promise_type>::from_promise(*p)) {}
~Generator() { handle.destroy(); }
bool next() { handle.resume(); return !handle.done(); }
T value() { return handle.promise().current_value; }
};
Generator<int> count_to_five() {
for (int i = 1; i <= 5; ++i) {
co_yield i;
}
}
int main() {
auto gen = count_to_five();
while (gen.next()) {
std::cout << gen.value() << std::endl;
}
}
非同期処理の実装
コルーチンを使うと、簡潔な非同期処理が可能です。
#include <coroutine>
#include <iostream>
#include <chrono>
#include <thread>
struct Task {
struct promise_type {
Task get_return_object() { return {}; }
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() { std::terminate(); }
};
};
Task async_function() {
std::cout << "Task started" << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "Task completed" << std::endl;
co_return;
}
int main() {
async_function();
std::cout << "Main continues" << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(2));
return 0;
}
コルーチンを使ったパイプライン処理
コルーチンを使うと、データ処理のパイプラインをシンプルに記述できます。
(省略: パイプライン処理の具体例を追加可能)