はかせのラボ

私の頭の中を書いていく雑記ブログです

C++ タスクシステム③ ~コルーチン~

あいさつ

どうも、はかせです。
今回はコルーチンの続きです。
まだ記事とかを写経して詳細をググっている途中で
あまり理解できていないのですがなかなかに楽しいです。

Corutine traits

C++でコルーチンを自前実装する際に必要なものです。
traitsは特徴という意味があり直訳するとコルーチンの特徴となります。
Coroutine traitsを実装した構造体を戻り値とするメソッドが
C++におけるコルーチンとなります。

Coroutine traitsは名前で解決してるっぽくて名前があってる必要があります。
逆に名前さえあっていればいいので小難しい継承関係がないです。

promise_type

コルーチンの挙動を設定する構造体です。
この中にコルーチンとして必要な機能を記述していきます。

get_return_object()

必ず実装しなければいけないメソッドです。
このメソッドはpromise_typeを実装した構造体を返すメソッドです。
他にも返したいものがあれば返していいらしいです。

auto get_return_object() { return MyCoroutine(HandleType::from_promise(*this)); }

initial_suspend(),final_suspend()

コルーチンの開始時と終了時に呼ばれるメソッドです。
suspend_alwaysもしくはsuspend_neverのどちらかを戻り値とします。
suspend_alwaysは開始時には実行状態ではなく動作させなければなりません。
suspend_neverは開始時から実行状態です。

alwaysは通常コルーチンに、neverはawait用コルーチンに使用するそうです。

//コルーチンの開始
std::experimental::suspend_always initial_suspend() { return {}; }
//コルーチンの終了
std::experimental::suspend_always final_suspend() { return {}; }

yield_value, return_value

値を返すメソッドです。
yield_valueはco_yieldが呼ばれたときに、
return_valueはco_returnとco_awaitが呼ばれたときにそれぞれ動作します。

//co_yieldを使うために必要な関数
std::experimental::suspend_always yield_value(T value)
{
	this->value = value;
	return {};
}

通常コルーチンはyield_valueのみ、await用コルーチンはreturn_valueのみ実装するのが普通だそうです。

coroutine_handle

コルーチンの状態を管理するものです。
coroutine_handle::from_promiseメソッドを使用して取得します。
コードはpromise_typeのところで書いたので省略です。

コンストラク

コピー禁止、ムーブ可能で実装します。
デストラクタでcoroutine_handleを破棄します。

//明示的に実装
//意図しないタイミングで呼ばれないようにする
explicit MyCoroutine(HandleType handle) :mHundle(handle) {}
//コピーは禁止
//しようとしたら破棄する
MyCoroutine(const MyCoroutine& coroutine) = delete;
MyCoroutine(MyCoroutine&& coroutine):mHundle(coroutine.mHundle)
{
	coroutine.mHundle = nullptr;
}
~MyCoroutine()
{
	if (mHundle) mHundle.destroy();
}

プラスアルファ

今までのでコルーチンとしての最低限の実装は終わりました。
あとは使いやすくするための機能を追加します。
今回は値の取得とコルーチンを進めるメソッドを追加します。

//現在の値を返す
T GetCurrentValue() const { return mHundle.promise().value; }
//コルーチンを次へ進める
bool MoveNext()
{
	mHundle.resume();
	return !mHundle.done();
}

値はcoroutine_handleから取得できます。
resumeはコルーチンの一時停止を解除して進めるメソッドです。
doneはコルーチンが終了したかを返してくれるメソッドです。
今回は終了していなければtrueを返しています。

あとがき

今回コルーチンの自前実装でした。
基本的には前回紹介したgeneratorで事足りそうですが、
足りなかった場合は今回みたく自前実装する必要があります。

おまじない的に書くことが多く説明も難しい言葉でされていることが多く
億劫になってしまいますが、そこまで難しくはなかったです。

次回以降はこの自前実装の非同期パターンをやってみたいと思っています。
それでは今回はこの辺でノシ

参考

qiita.com