[[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 が結合される。

トップ   編集 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 検索 最終更新   ヘルプ   最終更新のRSS