続 Fiberを使ってみる
昨日の続きです。
generatorの実装のため、coroutineに戻り値と中断処理を追加しました。
// コルーチン終了/中断時に投げられる class exit_exception : public std::exception {}; namespace coro_detail { enum state { none, // 通常 exiting, // 呼び出し元による中断を実行中 exited // 終了した }; } // namespace coro_detail template<class T> class coroutine { public: class self; friend class self; // Boost.Coroutineとの互換性のためネストしたクラスに変更 class self { public: self(coroutine* c) : coro_(c) {} // tは呼び出し元に戻す値 void yield(const T& t) { coro_->t_ = t; coro_->callee_.yield_to(coro_->caller_); if (coro_->state_ == coro_detail::exiting) throw exit_exception(); } // コルーチンの終了 __declspec(noreturn) void exit() { throw exit_exception(); } private: coroutine* coro_; }; typedef T (*coro_func_ptr)(self&); coroutine(std::size_t stack_size, coro_func_ptr func) : func_(func), state_(coro_detail::none) , callee_(stack_size, startup, this) { } ~coroutine() { // コルーチンの実行が完了していない場合は例外で終了させる if (state_ != coro_detail::exited) { state_ = coro_detail::exiting; caller_.yield_to(callee_); } } void yield() { caller_.yield_to(callee_); if (state_ == coro_detail::exiting) state_ = coro_detail::exited; } // コルーチンの結果を取得する // 実装を楽にするため、yield()と処理を分けた const T& result() const { return t_; } // コルーチンが終わったかどうか bool exited() const { return state_ == coro_detail::exited; } private: T t_; coro_func_ptr func_; coro_detail::state state_; fiber caller_; fiber callee_; static void CALLBACK startup(void* data) { coroutine* coro = static_cast<coroutine*>(data); self self(coro); try { coro->t_ = (*coro->func_)(self); // コルーチンがreturnした場合はこっちを通る coro->callee_.yield_to(coro->caller_); // 2006/08/29 18:58 追加 } catch (...) { // コルーチンがexit()した場合はこっちを通る } coro->state_ = coro_detail::exited; coro->callee_.yield_to(coro->caller_); } };
これを使うとgeneratorの実装は簡単です。
以前、スレッドでやったときと同じです。
template<class T> class generator : public boost::iterator_facade< generator<T>, const T, boost::single_pass_traversal_tag > { friend class boost::iterator_core_access; public: typedef typename coroutine<T>::self self; generator() { } explicit generator(typename coroutine<T>::coro_func_ptr func) : coro_ptr_(new coroutine<T>(0, func)) { increment(); } private: boost::shared_ptr<coroutine<T> > coro_ptr_; const T& dereference() const { return coro_ptr_->result(); } void increment() { coro_ptr_->yield(); if (coro_ptr_->exited()) coro_ptr_.reset(); } bool equal(const generator& rhs) const { return coro_ptr_ == rhs.coro_ptr_; } };
あとは関数ポインタだけでなく、ファンクタを渡せるようにすればほぼ完成なのですが、いくつか問題が見つかりました。
- ファンクタの扱いを楽にしようとしてboost::functionを使うと、Borland 5.82でコンパイルが通らない
- 実装に使っている関数ConvertFiberToThread()がWindowsXP以降でないと使えない
もう少し調整が必要です。