streambuf の最小実装 †前節(C++ iostream 実装資料1)でiostreamに渡すために必要な streambuf で実装すべきものを列挙した。ここではそれらの詳細を検討 し、最小実装で必要なのは
であることを述べる。 pbackfail を実装しない(default 動作、 すなわち traits::eof() を返す)ことにより、putback や ungetc を行うと badbit が設定される可能性があるが、 読み込み buffer に backup 域を 設けて buffer pointer を setg() を使って適切に設定することに より、putback や ungetc は default で(backup buffer 域を 使い切るまでは)動作する。 なお、underflow() 中で streambuf の setg() や setp() を使って正しく buffer pointer の位置を設定するのであれば、 uflow() は実装する必要は無い(既定動作は underflow() を 呼び出し、traits::eof() でなければ、読み出し位置を 1 進め underflow() の戻り値を uflow() の戻り値とし、もし underflow() が traits::eof() を返した場合は、traits::eof() を戻り値とする)。 pubsync †これは規格では streambuf の sync を呼び出すだけである。 すなわち、sync を実装すればよい。
は書き込み領域に残って いるデータを全部deviceへ出力する。失敗したら -1 を返し、 成功したらそれ以外(通常は 0)を返す。 in_avail †streamsize in_avail() は traits::eof() に至らずに読み出せる文字数の推測値 (最小保証値)を返すものである。 これが正の値を返す時は、 少なくともその数だけの文字は traits::eof() に至らずに読み出せる。 規格では、「読込み位置が使用可能な場合、egptr() - gptr() を返す。それ以外の場合 shomanyc() を返す。」とある。
は default で 0 を返すので、そのままにしておいてよいはずである。 snextc、sbumpc、sgetc 及び sgetn †
は一文字読み込みのための関数で、各々読み出し位置の扱いが 異なる。
多文字を読み出す
は xsgetn(s, n) を呼び出す。xsgetn は sbumpc の繰り返し で得られるものと等価とされている。派生 class でより効率の良い xsgetn を上書き実装してもよいとされている。 というわけで、uflow() と underflow() を実装すれば、 これらの関数を実装する必要は無い(defaultのままでよい)。 sputc と sputn †
は c を書き出し位置に出力した後、書き出し位置を一つ進める。 default では書き出し位置が利用できない場合 overflow(traits::to_int_type(c))を返す。 多文字書き出し関数である
は xsputc(s, n) を返し、xsputc(s, n) は繰り返し sputc(c) を 呼ぶのと等価とされている。xsgetn 同様、派生class で より効率の良い xsputn を上書き実装してよいとされている。 というわけで overflow() を実装すればよい。 sputbackc と sungetc †
Default 動作では、書き戻し位置が使用可能である時は、次位置 pointer を1つ戻す ことで対応できる場合は、これを行う。これができない場合、 すなわち次のいずれかの場合
これらの場合は、いずれも
を呼び出す。戻せない時には traits::eof() を返すことになっている。 これはdefault 動作なので、このままにしておく。 この節のまとめ †以上の考察から、最小実装で必要なのは
の4つである。なお、uflow() の default 動作は、underflow() を呼び出し、 結果が正常(traits::eof() 以外を返す)の場合は、その返却値を uflow() の 返却値として読み出し位置を1文字分進めて戻り、そうで無い場合は traits::eof() を返すので、underflow() 中で setg()、setp() を使って、buffer 位置を正しく 設定するならば uflow() を実装する必要は無い。 余談 †もういちど fstream と sstream を読み直して、mystream なるものを 作ってみる。基本的には
を作る。 #include <istream> #include <ostream> #include <iostream> namespace mynames { class mybuffer : public std::streambuf { public: mybuffer() {m_myid = 0;}; virtual ~mybuffer() {m_myid = 0;}; private: int m_myid; }; class imystream : public std::istream { public: imystream() : std::istream(0), m_mybuf() { this->init(&m_mybuf); }; private: mybuffer m_mybuf; }; class omystream : public std::ostream { public: omystream() : std::ostream(0), m_mybuf() { this->init(&m_mybuf); }; private: mybuffer m_mybuf; }; class mystream : public std::iostream { public: mystream() : std::iostream(0), m_mybuf() { this->init(&m_mybuf); }; private: mybuffer m_mybuf; }; } とりあえず例えば上の記述ファイルを mystream.h として #include "mystream.h" using namespace mynames; int main() { mystream mystrm; char linbuf[256]; while(std::cin.getline(linbuf,256)) { mystrm << linbuf << std::endl; std::cout << mystrm; } }; というプログラムを作れば動く。 istreamでは入力用buffer、ostreamでは 出力用buffer、iostreamでは両方のbufferが必要であるが、これは もちろん別々に作ってもよいと思われるが、fstreamやstringstreamでは 両用にしておいて、constructorでstd::io_base::in や std::io_base::out など modeを渡すことで、どちらの(あるいは両方の)bufferを確保するべきかを 決めている。 重要なのは各 stream class の constructor での init() の呼び出しで、override してなければ basic_ios<> により提供されており (g++ 3.2.2 の場合は bits/basic_ios.h 及び bits/basic_ios.tcc) この呼び出しにより、stream と streambuf が結合される。 |