C++のiostream

streambuf の最小実装

前節(C++ iostream 実装資料1)でiostreamに渡すために必要な streambuf で実装すべきものを列挙した。ここではそれらの詳細を検討 し、最小実装で必要なのは

  • sync
  • underflow
  • uflow (条件付きで不要。下記の「なお」書きを見よ。)
  • overflow

であることを述べる。

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 を実装すればよい。

  • int sync()

は書き込み領域に残って いるデータを全部deviceへ出力する。失敗したら -1 を返し、 成功したらそれ以外(通常は 0)を返す。

in_avail

streamsize in_avail() は traits::eof() に至らずに読み出せる文字数の推測値 (最小保証値)を返すものである。 これが正の値を返す時は、 少なくともその数だけの文字は traits::eof() に至らずに読み出せる。

規格では、「読込み位置が使用可能な場合、egptr() - gptr() を返す。それ以外の場合 shomanyc() を返す。」とある。

  • streamsize showmanyc()

は default で 0 を返すので、そのままにしておいてよいはずである。

snextc、sbumpc、sgetc 及び sgetn

  • 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

  • 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

  • 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 動作なので、このままにしておく。

この節のまとめ

以上の考察から、最小実装で必要なのは

  • 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() を実装する必要は無い。

余談

もういちど 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
Last-modified: 2009-08-28 (金) 14:29:58