続 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以降でないと使えない

もう少し調整が必要です。