続・続・ streambuf と iostream

http://d.hatena.ne.jp/janus_wel/20100616/1276702908 の続き。とりあえず抑えるべきことは抑えたぽいので実際に code を書いてみる。何かの device に書き込むというのもちょっとめんどくさいので今回は入力専用で alphabet を順に返す stream を定義してみる。

#include <iostream>
#include <iterator>
#include <streambuf>
#include <algorithm>
#include <functional>

class alphastreambuf : public std::streambuf {
    private:
        static const unsigned int numof_alphabets = 26;
        char alphabets[numof_alphabets];

    public:
        alphastreambuf(void) {
            traits_type::copy(alphabets, "abcdefghijklmnopqrstuvwxyz", numof_alphabets);
            setg(alphabets, alphabets, alphabets + numof_alphabets);
        }

    protected:
        pos_type seekoff(off_type off, std::ios_base::seekdir way,
                std::ios_base::openmode which = std::ios_base::in | std::ios_base::out) {
            if ((which & std::ios_base::in) == std::ios_base::in) {
                char* target;
                switch (way) {
                    case std::ios_base::beg:    target = eback() + off; break;
                    case std::ios_base::cur:    target = gptr() + off;  break;
                    case std::ios_base::end:    target = egptr() - off - 1;
                                                break;
                    default:                    return pos_type(off_type(-1));
                }

                if (target < eback() || egptr() <= target)
                    return pos_type(off_type(-1));

                if (gptr() != target) setg(eback(), target, egptr());

                return target - eback();
            }
            return pos_type(off_type(-1));
        }

        pos_type seekpos(pos_type sp,
                std::ios_base::openmode which = std::ios_base::in | std::ios_base::out) {
            return seekoff(off_type(sp), std::ios_base::beg, which);
        }
};

struct ialphastream : public std::istream {
    ialphastream(void) : std::istream(new alphastreambuf()) {}
    ~ialphastream(void) { delete rdbuf(); }
};

int main(void) {
    try {
        ialphastream ain;
        std::istream_iterator<char> iitr(ain), end;
        std::ostream_iterator<char> oitr(std::cout, "\n");
        std::copy(iitr, end, oitr);

        if (!ain.good()) {
            ain.clear();

            ain.seekg(10);
            std::cout << ain.tellg() << std::endl;

            std::istream_iterator<char> iitr(ain);
            std::copy(iitr, end, oitr);
        }
    }
    catch (const std::exception& ex) {
        std::cerr << ex.what() << std::endl;
    }

    return 0;
}

streambuf 自体が内部に固定幅の buffer を持つようにしてみた。あと underflow() を定義してないので最後の "z" を出力したあとに default 動作である traits_type::eof() が返ってこの stream に failbit が立つ。 if (!ain.good()) { で failbit が立っている場合の処理をしているんだけどここで seekg() と tellg() を呼び出していてこいつらは最終的に alphastreambuf の seekpos() / seekoff() を呼び出すのでその定義をしてやっている、というあたり。

でまぁここで、例えば以下のような underflow() を alphastreambuf に定義してやると、

int_type underflow(void) {
    setg(eback(), eback(), egptr());
    return traits_type::to_int_type(*gptr());
}

traits_type::eof() が返ることがなくなる、つまりこの stream に底がなくなるということなので最初の std::copy() のところで延々 a 〜 z を吐き出し続ける。今回は alphabet なので意味がないんだけど例えば one-time password 発生器みたいなものとかをこういう仕組みで定義すると stream から抽出するだけで OK みたいな、そういう設計もできるはず。