[[C++のiostream]] * streambuf の最小実装 [#g9e50fec] 前節([[C++ iostream 実装資料1]])でiostreamに渡すために必要な streambuf で実装すべきものを列挙した。ここではそれらの詳細を検討 し、最小実装で必要なのは - sync - underflow - uflow (条件付きで不要。下記の「なお」書きを見よ。) - overflow であることを述べる。 ただし pbackfail を実装しない(default 動作、 pbackfail を実装しない(default 動作、 すなわち traits::eof() を返す)ことにより、putback や ungetc を行うと badbit が設定される。 を行うと 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 [#hd465e26] これは規格では streambuf の sync を呼び出すだけである。 すなわち、sync を実装すればよい。 - int sync() は書き込み領域に残って いるデータを全部deviceへ出力する。失敗したら -1 を返し、 成功したらそれ以外(通常は 0)を返す。 ** in_avail [#sa35cbb5] streamsize in_avail() は traits::eof() に至らずに読み出せる文字数の推測値 (最小保証値)を返すものである。 これが正の値を返す時は、 少なくともその数だけの文字は traits::eof() に至らずに読み出せる。 規格では、「読込み位置が使用可能な場合、egptr() - gptr() を返す。それ以外の場合 shomanyc() を返す。」とある。 - streamsize showmanyc() は default で 0 を返すので、そのままにしておいてよいはずである。 ** snextc、sbumpc、sgetc 及び sgetn [#gaaa9d82] - int_type snextc() - int_type sbumpc() - int_type sgetc() は一文字読み込みのための関数で、各々読み出し位置の扱いが 異なる。 - snextc は読み出し位置を一文字分進めた後、現在読み出し位置の 文字を返す。 規格では、「効果: sbumpc() を呼び出す。 返却値: sbumpc() が traits::eof() を返した時は traits::eof() を 返し、そうでない場合、sgetc() を返す。」とある。 - sbumpc は現在読み出し位置の文字を返し、読み出し位置を 一文字分進める。規格では「返却値:入力列の読込み位置が使用不可能な場合、 uflow() を返す。そうでない場合、traits::to_int_type(*gptr())を返し、 入力列の次位置ポインタを一つ進める。」とある。 - sgetc は現在読み出し位置の文字を返す。読み出し位置は変化 しない。規格では「返却値:入力列の読込み位置が使用不可能な場合、 underflow() を返す。そうでない場合、traits::to_int_type(*gptr())を返す。」 とある。 多文字を読み出す - streamsize sgetn(char_type* s, streamsize n) は xsgetn(s, n) を呼び出す。xsgetn は sbumpc の繰り返し で得られるものと等価とされている。派生 class でより効率の良い xsgetn を上書き実装してもよいとされている。 というわけで、uflow() と underflow() を実装すれば、 これらの関数を実装する必要は無い(defaultのままでよい)。 ** sputc と sputn [#t371fc4d] - int_type sputc(char_type c) は c を書き出し位置に出力した後、書き出し位置を一つ進める。 default では書き出し位置が利用できない場合 overflow(traits::to_int_type(c))を返す。 多文字書き出し関数である - streamsize sputn(const char_type* s, streamsize n) は xsputc(s, n) を返し、xsputc(s, n) は繰り返し sputc(c) を 呼ぶのと等価とされている。xsgetn 同様、派生class で より効率の良い xsputn を上書き実装してよいとされている。 というわけで overflow() を実装すればよい。 ** sputbackc と sungetc [#b68791e7] - int_type sputbackc(char_type c) - int_type sungetc() Default 動作では、書き戻し位置が使用可能である時は、次位置 pointer を1つ戻す ことで対応できる場合は、これを行う。これができない場合、 すなわち次のいずれかの場合 - pointer が null である場合 - sputbackc(char_type c) の引数 c が書き戻し位置にある内容と異なる場合 これらの場合は、いずれも - int_type pbackfail(int_type c = traits::eof()) を呼び出す。戻せない時には traits::eof() を返すことになっている。 これはdefault 動作なので、このままにしておく。 ** この節のまとめ [#od2d5f5c] 以上の考察から、最小実装で必要なのは - int sync() - int_type underflow() - int_type uflow() - int_type overflow(int_type c = traits::eof()) の4つである。なお、uflow() の default 動作は、underflow() を呼び出し、 結果が正常(traits::eof() 以外を返す)の場合は、その返却値を uflow() の 返却値として読み出し位置を1文字分進めて戻り、そうで無い場合は traits::eof() を返すので、underflow() 中で setg()、setp() を使って、buffer 位置を正しく 設定するならば uflow() を実装する必要は無い。 ** 余談 [#r22c4229] もういちど fstream と sstream を読み直して、mystream なるものを 作ってみる。基本的には - streambuf の派生class mybuffer - mybufferをメンバーとする istreamの派生class - mybufferをメンバーとする ostreamの派生class - mybufferをメンバーとする iostreamの派生class を作る。 #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 が結合される。