![]() |
コルーチンとは、処理を中断したり、再開したりすることのできるサブルーチンの変種である。
Hamigaki.Coroutineはコルーチンを作成するためのクラステンプレートcoroutine
を提供している。ここではcoroutine
を用いて掛け算九九の答えを表示するプログラムを考える。
#include <hamigaki/coroutine/coroutine.hpp>
これはcoroutine
を使用するために必要である。
namespace coro = hamigaki::coroutines; typedef coro::coroutine<int()> coroutine_type;
coroutine
は名前空間hamigaki::coroutines
で定義される。唯一のテンプレート引数はコルーチンの型を指定するためのものである。ここではint
型の戻り値を持ち、引数を持たないコルーチンを使用する。
int kuku_body(coroutine_type::self& self) { for (int i = 1; i <= 9; ++i) for (int j = 1; j <= 9; ++j) self.yield(i*j); self.exit(); }
関数kuku_body()
はコルーチンの本体である。この関数の型はcoroutine
のテンプレート引数に指定したものにcoroutine_type::self&
型の引数を追加したものになる。この追加された引数はコルーチンの制御を行うために使用される。
メンバ関数yield()
はコルーチンの呼び出し側に制御を戻し、引数をコルーチンの計算結果として返す関数である。
また、メンバ関数exit()
はコルーチンの実行を中断し、呼び出し側に制御を戻す関数である。
#include <iostream> int main() { try { coroutine_type kuku(kuku_body); while (true) std::cout << kuku() << std::endl; } catch (const coro::coroutine_exited&) { } }
coroutine
のコンストラクタの引数にはコルーチンの本体を指定する。
作成されたcoroutine
は関数オブジェクトとして機能する。コルーチンを呼び出すと、制御がコルーチン本体へと移る。コルーチン側はyield()
が呼ばれるか、return
されるまで実行され、その結果が呼び出し側に返される。再びコルーチンを呼び出すと、前回yield()
を呼んだ箇所から実行が再開される。
メンバ関数exit()
が呼ぶか、コルーチンからreturn
すると、コルーチンは終了する。終了したコルーチンを呼び出すと、例外coroutine_exited
が発生する。コルーチンはその性質上、呼び出すまで終了したかどうか分からないので、この例外を捕捉することで終了を検知する。
別の方法として、コルーチンの呼び出し時にstd::nothrow
を引数として指定することもできる。この場合、戻り値がboost::optional
になり、終了を検知した場合は空となる。
#include <iostream> int main() { coroutine_type kuku(kuku_body); while (boost::optional<int> next = kuku(std::nothrow)) std::cout << *next << std::endl; }
完全なプログラムは以下のようになる。
#include <hamigaki/coroutine/coroutine.hpp> namespace coro = hamigaki::coroutines; typedef coro::coroutine<int()> coroutine_type; int kuku_body(coroutine_type::self& self) { for (int i = 1; i <= 9; ++i) for (int j = 1; j <= 9; ++j) self.yield(i*j); self.exit(); } #include <iostream> int main() { try { coroutine_type kuku(kuku_body); while (true) std::cout << kuku() << std::endl; } catch (const coro::coroutine_exited&) { } }
オブジェクトの列を生成し、一要素ずつ順に返すサブルーチンをジェネレータと呼ぶ。
Hamigaki.Coroutineはジェネレータを作成するためにクラステンプレートgenerator
を提供しており、これはコルーチンを用いて実装されている。今度は、先程の九九のプログラムをジェネレータを用いて書き直すことを考える。
#include <hamigaki/coroutine/generator.hpp>
これはgenerator
を使用するために必要である。
namespace coro = hamigaki::coroutines; typedef coro::generator<int> generator_type;
generator
のテンプレート引数はコルーチンの型ではなく、戻り値の型である。
int kuku_body(generator_type::self& self) { for (int i = 1; i <= 9; ++i) for (int j = 1; j <= 9; ++j) self.yield(i*j); self.exit(); }
関数kuku_body()
はコルーチンの本体である。これはcoroutine
がgenerator
に置き換わっただけである。
メインプログラムは次のように書ける。
#include <algorithm> #include <iostream> #include <iterator> int main() { std::copy( generator_type(kuku_body), generator_type(), std::ostream_iterator<int>(std::cout, "\n") ); }
generator
のコンストラクタにコルーチンの本体を渡すことでジェネレータを作成する。省略時初期化されたgenerator
は終端を表すために利用される。
generator
は入力反復子の要件を満たすので、入力反復子を受け取るアルゴリズムに渡すことができる。
C言語でオブジェクトを列挙する場合、主に次のインタフェースが利用される。
前者の例としては、POSIXのopendir()
/readdir()
/closedir()
を用いたディレクトリの走査が挙げられる。このインタフェースは列挙を進めることも中断することも自由に行うことができるため、反復子で表現するのが容易である。
一方、後者のインタフェースはMicrosoft Windowsで頻繁に用いられるものである。このインタフェースは、列挙の主体がユーザー側にないため、反復子で表現することは困難である。しかし、Hamigaki.Coroutineを用いることでこれが可能となる。
例として、ウィンドウハンドル(HWND)を列挙するEnumWindows()
関数を反復子で表現することを考える。
#include <hamigaki/coroutine/generator.hpp> #include <algorithm> #include <iostream> #include <windows.h> namespace coro = hamigaki::coroutines; typedef coro::generator<HWND> enum_windows_iterator; BOOL CALLBACK enum_windows_callback(HWND hwnd, LPARAM lParam) { try { enum_windows_iterator::self& self = *reinterpret_cast<enum_windows_iterator::self*>(lParam); self.yield(hwnd); return TRUE; } catch (...) { } return FALSE; } HWND enum_windows_body(enum_windows_iterator::self& self) { ::EnumWindows(&enum_windows_callback, reinterpret_cast<LPARAM>(&self)); self.exit(); }
enum_windows_body()
がコルーチンの本体である。EnumWindows()
はこの中で呼び出す。EnumWindows()
には各ウィンドウハンドル毎に呼び出すコールバック関数とそれに渡すデータを引数として渡すようになっている。コールバック関数enum_windows_callback()
はself
を必要とするので、ここではそのポインタを渡している。
enum_windows_callback()
では、yield()
でウィンドウハンドルを返し、制御を呼び出し側に戻す。
反復子が進められると処理がコルーチン側に戻り、コールバック関数からはTRUE
が返される。これは列挙の継続を意味している。
反復子を末尾まで進めることなく破棄した場合は、yield()
が例外exit_exception
を送出するので、これを捕捉した後にFALSE
を返す。これにより列挙も中断される。
enum_windows_iterator
は入力反復子なので、次のように利用できる。
void print_hwnd(HWND hwnd) { std::cout << static_cast<void*>(hwnd) << std::endl; } int main() { std::for_each( enum_windows_iterator(enum_windows_body), enum_windows_iterator(), print_hwnd ); }
製作著作 © 2006-2008 Takeshi Mouri |