ストリームの使い方の基本っぽいメモ。
基本は std::streambuf というクラスを継承した派生クラスをつくって、 それを std::istream (入力用)や std::ostream (出力用)で使う、 という使用法になる。
istream や ostream は、 C++ の初歩として学習する iostream の基本クラス。 ていうか、C++ の初っ端でいきなりこんな難しいものをワケも分からず使わされていたのかと。( <iostream> を include するのは標準入出力を使うときのお約束って習うよね。)
これはテンプレートクラス。何型のバッファを持つかということを指定できる。 大抵は char 型で持って文字列を扱う。
#define _MAX_BUFFER 64
template <class Ch, class Tr = std::char_traits<Ch> > class CBasicStreamBuffer : public std::basic_streambuf<Ch, Tr> { public: CBasicStreamBuffer(void) { memset( m_buffer, -1, _MAX_BUFFER ); setp( &(m_buffer[0]), &(m_buffer[_MAX_BUFFER - 1]) ); setg( &(m_buffer[0]), &(m_buffer[0]), &(m_buffer[_MAX_BUFFER - 1]) ); } ~CBasicStreamBuffer(void) { } protected: virtual int sync() { // TODO: フラッシュ処理を書く。 } private: Ch m_buffer [_MAX_BUFFER]; };
setp というのは入力用(書込み用)ポインタの初期化。
setp( バッファの先頭ポインタ, バッファの最後のポインタ );
setg は出力用(読取り用)ポインタの初期化。
setg( バッファの先頭ポインタ, バッファの現在のポインタ, バッファの最後のポインタ );
つまり m_buffer の先頭位置と最終位置を基底クラスのメソッドで設定しているのですな。これで基底クラスが入出力演算子でバッファの読み書きをしてくれるようになると。
ちなみに、-1 は、現在のバッファ終端を表す値(EOF)として扱われるので、コンストラクタで memset でこの値で初期化しておくと確実かも。
基底クラスの sync() は、バッファを外部データに反映させて内部バッファをクリアする処理を書く。 std::endl などのタイミングでコールされる。
virtual int sync() { // 書込みの位置に0を差し込む。 // 文字列ならこれで終端になるし。 *pptr() = 0; // 表示してみる(デバッグ用) printf( "%s", m_buffer ); // 書込み位置を先頭に戻す。 pbump( pbase() - pptr() ); // バッファを初期化。 memset( m_buffer, -1, _MAX_BUFFER ); return 0; }
とかね。
ストリームバッファを使う入出力ストリームも作っておきます。
template <class Ch, class Tr = std::char_traits<Ch> > class CBasicIOStream : public std::basic_iostream<Ch, Tr> { public: CBasicIOStream(void) : std::basic_iostream<Ch, Tr>( new CBasicStreamBuffer<Ch, Tr>() ) { } ~CBasicIOStream(void) { } };
とりあえず、これだけで入出力用のストリームバッファとして使えるです。
慣例に従って、char 型のストリームを typedef しておいて、と。
typedef CBasicStreamBuffer<char> CStreamBuffer; typedef CBasicIOStream<char> CIOStream;
こんな具合に使います。
Ex) CIOStream strm; // 入力の例 strm << "Hello World." << std::endl; // 出力の例 char buf [16]; strm >> buf; printf( "%s\n", buf );
ストリームに入力した内容をそのまま出力しただけ。
内部バッファを用意しなければ、入力をそのまま出力に回すだけのストリームができる。これはこれで使い道があるはず。
template <class Ch, class Tr = std::char_traits<Ch> > class CBasicStreamBuffer : public std::basic_streambuf<Ch, Tr> { public: CBasicStreamBuffer(void) { } ~CBasicStreamBuffer(void) { } protected: virtual int overflow( int nCh = EOF ) { } };
内部バッファがないので、コンストラクタで特別何かする必要はない。てか、しちゃだめ。やるべきは、overflow() をオーバーライドして入力を受け取る処理を書くこと。
overflow() は、入力バッファが1つ溢れる度にコールされる。この場合、コンストラクタで setp() していないので、書込みの先頭と終了のポインタが共に 0 のままのはず。ということは、書込みが発生する度に毎回このメソッドがコールされる。
こんな風に確認してみそ。
virtual int overflow( int nCh = EOF ) { char buf [2]; buf[0] = nCh; buf[1] = 0; printf( "%s", buf ); }
簡易的な説明。
入力(書込み)の先頭ポインタを返す。setp() の第1引数。
入力(書込み)の最終ポインタを返す。setp() の第2引数。
現在の入力(書込み)ポインタを返す。
現在の入力(書込み)ポインタを移動する。
出力(読込み)の先頭ポインタを返す。setg() の第1引数。
出力(読込み)の最終ポインタを返す。setg() の第3引数。
現在の出力(読込み)ポインタを返す。
現在の出力(読込み)ポインタを移動する。
フラッシュするときに呼び出される( std::endl などのタイミング)。オーバーライドしてフラッシュする処理を書く。
オーバーライドして書込み(入力)と読込み(出力)のポインタを移動する処理を書く(ostream::seekp() などのタイミング)。入出力両方のポインタを移動するようにすること。
バッファに書込みが発生したときに呼び出される。オーバーライドしてデータを1つ書き込んだりバッファを拡張したりする。書込みポインタを1つ進める。
バッファから読込みが発生したときに呼び出される。現在の読込み位置のデータを返して、読込みポインタを1つ進める。
読込みが失敗した(とされる)場合に呼び出される。現在の読込みポインタを1つ戻して、戻った位置のデータを返す。
バッファから読込みが発生したときに呼び出される。読み出すが、ポインタは現在位置のままにしたい場合はこちらが呼び出されるお約束。なので、読込みポインタは進めない。通常は、uflow() して pbackfail() することになっているらしい。