非対称コルーチン

2つの非対称コルーチン型があります。coroutine<>::push_typecoroutine<>::pull_typeです。一方向のデータの転送を提供します。

注釈

asymmetric_coroutine<>coroutineのtypedefです。

coroutine<>::pull_type

coroutine<>::pull_typeは別の実行コンテキストからデータを転送します(引き出します)。テンプレート引数は転送されるパラメーターの型を定義します。coroutine<>::pull_typeのコンストラクターは、coroutine<>::push_typeへの参照を引数にとる関数(コルーチン関数)を引数に取ります。coroutine<>::pull_typeをインスタンス化することで、実行の制御をコルーチン関数へと渡し、そして相補的なcoroutine<>::push_typeをライブラリが合成し、コルーチン関数へ参照として渡します。

この種のコルーチンはcoroutine<>::pull_type::operator()を提供しています。このメソッドはコンテキストスイッチをするだけで、データを転送しません。

coroutine<>::pull_typeは、入力イテレーター(coroutine<>::pull_type::iterator)とstd::begin()/std::end()のオーバーロードを提供します。インクリメント命令が、コンテキストの切り替え、並びにデータの転送を行います。

typedef boost::coroutines2::coroutine<int>   coro_t;

coro_t::pull_type source(
    [&](coro_t::push_type& sink){
        int first=1,second=1;
        sink(first);
        sink(second);
        for(int i=0;i<8;++i){
            int third=first+second;
            first=second;
            second=third;
            sink(third);
        }
    });

for(auto i:source)
    std::cout << i <<  " ";

output:
1 1 2 3 5 8 13 21 34 55

この例では、coroutine<>::pull_typeが、main実行コンテキストにおいて、フィボナッチ数列を単純なforループで計算するラムダ関数(==コルーチン関数)をとることで作成されています。コルーチン関数は、coroutine<>::pull_typeのインスタンスにより管理される新しく作成された実行コンテキストで実行されます。coroutine<>::push_typeはライブラリにより自動的に生成され、ラムダ関数へ参照として渡されます。ラムダ関数の中でcoroutine<>::push_type::operator()が別のフィボナッチ数とともに呼び出されるたびに、coroutine<>::push_typeがmain実行コンテキストへとそれを転送して戻ります。コルーチン関数のローカルな状態は保存され、次のフィボナッチ数を計算するべく元のコルーチン関数に実行制御を戻す際に復元されます。coroutine<>::pull_typeは入力イテレーターと std::begin()/std::end()のオーバーロードを提供しているので、範囲ベースforループを、生成されたフィボナッチ数列をイテレートするのに使うことが出来ます。

coroutine<>::push_type

coroutine<>::push_typeはデータを他の実行コンテキストへと転送します(押し入れます)。テンプレート引数は転送されるパラメーターの型を定義します。coroutine<>::push_typeのコンストラクターは、coroutine<>::pull_typeへの参照を引数にとる関数(コルーチン関数)を引数に取ります。coroutine<>::pull_typeと異なり、coroutine<>::push_typeをインスタンス化しても、コルーチン関数へ実行の制御を渡しません。その代わり、coroutine<>::push_type::operator()の最初の呼び出しが、相補的なcoroutine<>::pull_typeを合成し、コルーチン関数へ参照として渡します。

coroutine<>::push_typeのインターフェイスに、get()関数は含まれません。つまり、この種のコルーチンでは、別の実行コンテキストから値を受け取ることは出来ません。

coroutine<>::push_typeは出力イテレーター(coroutine<>::push_type::iterator)とstd::begin()/std::end()のオーバーロードを提供しています。インクリメント命令が、コンテキストの切り替え、並びにデータの転送を行います。

typedef boost::coroutines2::coroutine<std::string>   coro_t;

struct FinalEOL{
    ~FinalEOL(){
        std::cout << std::endl;
    }
};

const int num=5, width=15;
coro_t::push_type writer(
    [&](coro_t::pull_type& in){
        // 何が起きたとしても、最終行を終了する
        FinalEOL eol;
        // 上流から値を引き出して、num個を一行に展開する
        for (;;){
            for(int i=0;i<num;++i){
                // 入力を使い果たしたらやめる
                if(!in) return;
                std::cout << std::setw(width) << in.get();
                // この要素をハンドルし終わったので、次へ進む
                in();
            }
            // num個の要素の後、改行する
            std::cout << std::endl;
        }
    });

std::vector<std::string> words{
    "peas", "porridge", "hot", "peas",
    "porridge", "cold", "peas", "porridge",
    "in", "the", "pot", "nine",
    "days", "old" };

std::copy(begin(words),end(words),begin(writer));

output:
           peas       porridge            hot           peas       porridge
           cold           peas       porridge             in            the
            pot           nine           days            old

この例では、coroutine<>::push_typeが、main実行コンテキストにおいて、文字列をリクエストして各行にnum個並べるラムダ関数(==コルーチン関数)をとることで作成されています。これは、コルーチンにより制御の逆転が可能になることを示しています。コルーチンなしでは、同じ仕事をするユーティリティー関数が新しい値をそれぞれ引数として受け取って、1つの値について処理した後に戻らなければなりません。その関数は静的な状態変数に依存するでしょう。しかしコルーチン関数では、関数を呼ぶかのように新しい値をリクエストすることができます。呼び出し元が関数を呼ぶかのように値を渡しているのにも関わらずです。コルーチン関数は、coroutine<>::push_typeのインスタンスにより管理される新しく作成された実行コンテキストで実行されます。coroutine<>::pull_typeがライブラリにより自動的に生成され、ラムダ関数へ参照として渡されます。コルーチン関数は、main実行コンテキストから渡された文字列へとcoroutine<>::pull_type::get()を呼ぶことでアクセスして、パラメーターnumとwidthに従い、std::coutで並べて出力します。コルーチン関数のローカルな状態は保存され、元のコルーチン関数に実行制御を戻す際に復元されます。coroutine<>::push_typeは入力イテレーターと std::begin()/std::end()のオーバーロードを提供しているので、std::copyアルゴリズムを、文字列をいれたvectorをイテレートしてひとつひとつコルーチンに渡すのに使うことが出来ます。

コルーチン関数

コルーチン関数voidを返し、相対するコルーチンを引数として取ります。なのでコルーチン関数にとって、引数として渡されたコルーチンを使うことが、データを転送して実行制御を呼び出し元に戻す唯一の方法です。どちらのコルーチン型も、同じテンプレート引数を取ります。coroutine<>::pull_typeの場合、coroutine<>::pull_typeの作成時にコルーチン関数へと入ります。coroutine<>::push_typeの場合、coroutine<>::push_typeの作成時にコルーチン関数へと入りはしませんが、coroutine<>::push_type::operator()の最初の発動時に入ります。実行制御がコルーチン関数から戻った後、コルーチンがまだ有効(コルーチン関数が終了していない)かどうかをcoroutine<>::pull_type::operator booltrueを返すかどうかで確かめることが出来ます。1番目のテンプレート引数がvoidでない限り、trueはデータ値が利用可能であることも暗示します。

プル・コルーチンからmainコンテキストへのデータの転送

coroutine<>::pull_typeからmainコンテキストへデータを転送するために、フレームワークがmainコンテキストのcoroutine<>::pull_typeのインスタンスと関連付けられたcoroutine<>::push_typeを合成します。合成されたcoroutine<>::push_typeは、コルーチン関数へ引数として渡されます。コルーチン関数は、このcoroutine<>::push_type::operator()を、各データをmainコンテキストへ戻すために呼び出さなければなりません。mainコンテキストでは、coroutine<>::pull_type::operator boolにより、コルーチンがまだ有効であるか否か、すなわち、データ値が利用可能であるかコルーチン関数が終了したかのどちらなのかを確かめます(coroutine<>::pull_typeが無効であるなら、データ値は利用可能ではありません)。転送されたデータ値へのアクセスは、coroutine<>::pull_type::get()により行えます。

typedef boost::coroutines2::coroutine<int>   coro_t;

coro_t::pull_type source( // コンストラクターによりコルーチン関数へ入る
    [&](coro_t::push_type& sink){
        sink(1); // {1} をmainコンテキストへ押し戻す
        sink(1); // {1} をmainコンテキストへ押し戻す
        sink(2); // {2} をmainコンテキストへ押し戻す
        sink(3); // {3} をmainコンテキストへ押し戻す
        sink(5); // {5} をmainコンテキストへ押し戻す
        sink(8); // {8} をmainコンテキストへ押し戻す
    });

while(source){            // プル・コルーチンが有効であるか確かめる
    int ret=source.get(); // データ値へとアクセスする
    source();             // コルーチン関数へとコンテキストスイッチする
}

mainコンテキストからプッシュ・コルーチンへのデータの転送

mainコンテキストからcoroutine<>::push_typeへデータを転送するために、フレームワークがmainコンテキストのcoroutine<>::push_typeのインスタンスと関連付けられたcoroutine<>::pull_typeを合成します。合成されたcoroutine<>::pull_typeコルーチン関数へ引数として渡されます。mainコンテキストは、このcorotine<>::push_type::operator()を、各データをコルーチン関数へ転送するために呼び出さなければなりません。転送されたデータ値へのアクセスは、coroutine<>::pull_type::get()により行えます。

typedef boost::coroutines2::coroutine<int>   coro_t;

coro_t::push_type sink( // コンストラクターはコルーチン関数に入らない
    [&](coro_t::pull_type& source){
        for (int i:source) {
            std::cout << i <<  " ";
        }
    });

std::vector<int> v{1,1,2,3,5,8,13,21,34,55};
for( int i:v){
    sink(i); // {i} をコルーチン関数へ押し入れる
}

パラメーターへのアクセス

コルーチン関数から返されたか、あるいはコルーチン関数へと転送されたデータへのアクセスは、coroutine<>::pull_type::get()により行えます。

パラメーターへのアクセスをコンテキストスイッチ関数から分離したことで、coroutine<>::pull_typeが有効かの確認をcoroutine<>::pull_type::operator()から戻ってきたあとで行えるようになりました。例えば、coroutine<>::pull_typeが値を持ち、かつコルーチン関数が終了していないことを確認できます。

typedef boost::coroutines2::coroutine<boost::tuple<int,int>> coro_t;

coro_t::push_type sink(
    [&](coro_t::pull_type& source){
        // タプル {7,11} へアクセス; x==7 y==1
        int x,y;
        boost::tie(x,y)=source.get();
    });

sink(boost::make_tuple(7,11));

例外

coroutine<>::pull_typeコルーチン関数内部で、coroutine<>::push_type::operator()の最初の呼び出しの前に投げられた例外は、coroutine<>::pull_typeのコンストラクターにより再スローされます。coroutine<>::pull_typeコルーチン関数の最初の呼び出しの後に、そのコルーチン関数内部で投げられる例外は全て、coroutine<>::pull_type::operator()により再スローされます。coroutine<>::pull_type::get()は例外を投げません。

coroutine<>::push_typeコルーチン関数の内側で投げられた例外は、coroutine<>::push_type::operator()により再スローされます。

重要

コルーチン関数により実行されるコードは、detail::forced_unwind例外の伝播を妨げてはなりません。その例外を吸収してしまうと、スタックの巻き戻しの失敗を引き起こします。従って、すべての例外をキャッチするコードは全て、保留した全てのdetail::forced_unwind例外を再スローしなくてはなりません。

try {
    // スローするかも知れないコード
} catch(const boost::coroutines2::detail::forced_unwind&) {
    throw;
} catch(...) {
    // 保留した例外を再スローしなくとも良い
}

重要

catchブロック内から別の実行コンテキストへとjumpせずに、例外を再スローしてください。

スタックの巻き戻し

時には、ローカルスタック変数を破壊して割り当てられた資源を解放する(RAIIパターン)ために、未終了のコルーチンのスタックを巻き戻す必要があります。コルーチンのコンストラクターのattributes引数は、デストラクターがスタックを巻き戻すべきかどうかを示しています(デフォルトではスタックは巻き戻されます)。

スタックの巻き戻しは以下を前提条件としています:

  • コルーチンが「コルーチンでない」状態ではない
  • コルーチンが完了していない
  • コルーチンが実行中ではない
  • コルーチンがスタックを持つ

巻き戻しの後、コルーチンは完了します。

struct X {
    X(){
        std::cout<<"X()"<<std::endl;
    }

    ~X(){
        std::cout<<"~X()"<<std::endl;
    }
};

{
    typedef boost::coroutines2::coroutine<void>::push_type   coro_t;

    coro_t::push_type sink(
        [&](coro_t::pull_type& source){
            X x;
            for(int=0;;++i){
                std::cout<<"fn(): "<<i<<std::endl;
                // 実行制御をmain()へと戻します
                source();
            }
        });

    sink();
    sink();
    sink();
    sink();
    sink();

    std::cout<<"sink is complete: "<<std::boolalpha<<!sink<<"\n";
}

output:
    X()
    fn(): 0
    fn(): 1
    fn(): 2
    fn(): 3
    fn(): 4
    fn(): 5
    sink is complete: false
    ~X()

範囲イテレーター

Boost.Coroutine2は、__boost_range__を用いた出力イテレーターと入力イテレーターを提供します。coroutine<>::pull_typeは、入力イテレーターを介して、std::begin()std::end()を用いて使用可能です。

typedef boost::coroutines2::coroutine< int > coro_t;

int number=2,exponent=8;
coro_t::pull_type source(
    [&](coro_t::push_type & sink){
        int counter=0,result=1;
        while(counter++<exponent){
            result=result*number;
            sink(result);
        }
    });

for (auto i:source)
    std::cout << i <<  " ";

output:
    2 4 8 16 32 64 128 256

coroutine<>::pull_type::iterator::operator++()coroutine<>::pull_type::operator()と対応していて、coroutine<>::pull_type::iterator::operator*()coroutine<>::pull_type::get()と概ね対応しています。coroutine<>::pull_type::operator boolfalseを返すとき、元々はcoroutine<>::pull_typestd::begin()から得たイテレーターと、同じcoroutine<>::pull_typeのインスタンスのstd::end()で得たイテレーターは等しくなります。

注釈

もしTがムーブ・オンリーな型であるなら、coroutine<T>::pull_type::iteratorの参照外しは、再びインクリメントされるまで1度だけしか出来ません。

出力イテレーターは、coroutine<>::push_typeにより作成可能です。

typedef boost::coroutines2::coroutine<int>   coro_t;

coro_t::push_type sink(
    [&](coro_t::pull_type& source){
        while(source){
            std::cout << source.get() <<  " ";
            source();
        }
    });

std::vector<int> v{1,1,2,3,5,8,13,21,34,55};
std::copy(begin(v),end(v),begin(sink));

coroutine<>::push_type::iterator::operator*()は、coroutine<>::push_type::operator()と概ね対応しています。coroutine<>::push_type::operator boolfalseを返すとき、元々はcoroutine<>::push_typestd::begin()から得たイテレーターと、同じcoroutine<>::push_typeのインスタンスのstd::endで得たイテレーターは等しくなります。

コルーチン関数の終了

コルーチン関数を終了するには、単純なreturn文により呼び出しルーチンに戻ります。coroutine<>::pull_typeあるいはcoroutine<>::push_typeが完了したら、coroutine<>::pull_type::operator boolあるいはcoroutine<>::push_type::operator boolfalseを返します。

重要

コルーチン関数から戻った後は、コルーチンは完了しています(coroutine<>::push_type::operator()coroutine<>::pull_type::operator()で再開できません)。

results matching ""

    No results matching ""