(このコンテンツはメールマガジンの STL & iostream 入門に手を加えたものです。「 STL と iostream が使えるかのチェック」等はメールマガジンの方のページをご覧ください)
|
iostream の仕組み ( #16 ) |
STL がそうだったように、 iostream も非常に高い「カスタマイズ性」を持っています。「ないものは作ればいい」に強く応えてくれます。ですが、そのためにはまず iostream 全体の構造を知っておく必要があります。
iostream のクラスは、だいたい3つのグループに分かれます。これは「ストリームクラス」「フォーマットクラス」「バッファクラス」です。 これまで紹介してきた std::strstream などのクラスは、すべて最初の「ストリームクラス」に属するクラスです。普段使用する場合(つまりカスタマイズする必要がない場合)にはこのストリームクラスしかお世話にならないというわけです。 このストリームクラスは、「フォーマットクラス」と「バッファクラス」を組み合わせたものです。つまり ストリームクラス = フォーマットクラス + バッファクラス ということです。カスタマイズの第一歩は、これらを分解して考えることです。これを std::strstream と std::fstream のふたつのストリームクラスで見てみると、次のようになります。 Stream Class = Format Class + Buffer Class ―――――――――――――――――――――――― std::istrstream = std::istream + std::strstreambuf std::ostrstream = std::ostream + std::strstreambuf std::strstream = std::iostream + std::strstreambuf ―――――――――――――――――――――――― std::ifstream = std::istream + std::filebuf std::ofstream = std::ostream + std::filebuf std::fstream = std::iostream + std::filebuf std::istream std::ostream std::iostream は「フォーマットクラス」です。これらフォーマットクラスは、 << や >> 演算子を持っていて、文字列形式への整形を行います。 std::istream は >> を持ちます。 >> を使うと、まず「バッファクラス」から文字列形式として取り出し、それを指定された型へと変換して値を返します。 std::ostream は << を持ちます。 << を使うと、渡された値を文字列形式へと変換して、それを「バッファクラス」へと書き込みます。 std::iostream はこのふたつを組み合わせたものです。 重要なのは、フォーマットクラスは文字配列やファイルへと直接アクセスしないということです。フォーマットクラスは文字列形式への変換を行い、その文字列形式のデータを「バッファクラス」とやりとりするだけです。 実際に文字配列やファイルへとアクセスするのは「バッファクラス」の方です。バッファクラスは文字配列やファイルなどと生のデータのやりとりを行い、それを文字列形式へと相互変換してフォーマットクラスとのやりとりに使用します。 std::strstreambuf は文字配列とアクセスするクラスです。 std::filebuf はファイルとアクセスするクラスです。 つまり「データ形式の変換」と「操作対象へのアクセス」を別々のクラスに分けているのです。これが iostream の仕組みです。 このおかげで、 std::strstream も std::fstream も同じように操作することができます。また、他の操作対象(例えばプリンタとか)に iostream を使用したい場合には、専用のバッファクラスを作って、フォーマットクラスと関連づけられているのです。 「関連づけ」については、例を見た方がいいかもしれません。では、 std::strstream と同様の機能を、 std::iostream と std::strstreambuf を関連づけることで実現してみましょう。 //////////////////////////////////////////////////////////////// // std::strstream のニセモノを作ります。 #include <stdio.h> #include <iostream> #include <strstream> void MakeFake_strstream() { char ch[130]; std::strstreambuf cStrStrmBuf( ch, 128, ch ); // バッファクラス。 std::iostream cIOStrm( &cStrStrmBuf ); // ストリームクラス。 cIOStrm << "MakeFake_strstream()" << std::endl << std::ends; printf( "%s", ch ); } // 結果 MakeFake_strstream() ///////////////////////////// どうでしょう、 std::strstream の時に似てる、と思いません? 違うのは、 std::strstream 型の代わりに std::strstreambuf と std::iostream の変数を作っているという点です。 まず「バッファクラス」型の変数を作ります。入出力の相手は文字配列なので、文字配列用のバッファクラス std::strstreambuf を使います。 このとき渡す引数は、第1引数と第2引数は std::strstream とほぼ同じです。第1引数には入力の相手になる文字配列へのポインタを、第2引数には文字配列のサイズを指定します。 第3引数はちょっと違って、出力先のポインタを指定します。これは std::strstream とちょっと違いますね。実は第1引数は「読み取りポインタ」を、第3引数は「書き込みポインタ」を指定します。 std::strstream では読み取りポインタと書き込みポインタは独立していました。 std::strstreambuf を直接初期化するときには、別々に設定できるというわけです。ここでは std::strstream と同じように、両方とも同じポインタをセットします。 バッファクラスが作れたらフォーマットクラスの std::iostream 型変数を作って、バッファクラスへのポインタを渡します。ここでは当然 std::strstreambuf 型変数へのポインタを渡します。このポインタを通して、フォーマットクラスはバッファクラスへとアクセスします。 これで std::strstream のニセモノが完成しました。 std::iostream 型変数は std::strstream とほとんど同じように使うことができます。 |
iostream の組み合わせ例 ( #17 ) |
前回は「ストリームクラスはフォーマットクラスとバッファクラスの組み合わせ」ということを紹介しました。
もうすこし正確に言うと「ストリームとして使用するためには、フォーマットクラスにバッファクラスへのポインタを渡す必要がある」ということになります。面白い例を見てみましょう。 //////////////////////////////////////////////////////////////// // std::iostream の使用例です。 #include <stdio.h> #include <iostream> #include <fstream> #include <strstream> // バッファクラスを受け取ってフォーマットクラスに関連づけます。 bool UseFake_iostream( std::streambuf *p_pcStrmBuf ) { std::iostream cIOStrm( p_pcStrmBuf ); cIOStrm << "UseFake_iostream()" << std::endl << std::ends; return true; } // 上の関数の使用例。 void MakeFake_steambuf() { // std::strstream もどき。 char ch[130]; std::strstreambuf cStrStrmBuf( ch, 128, ch ); UseFake_iostream( &cStrStrmBuf ); printf( "%s", ch ); // std::fstream もどき。 std::filebuf cFBuf; cFBuf.open( "Data.txt", std::ios::out ); UseFake_iostream( &cFBuf ); } ///////////////////////////// UseFake_iostream() は受け取ったバッファクラスにフォーマットクラスを結びつけて << 演算子を使って書き込む関数です。呼び出す側では、文字配列用の std::strstreambuf と、ファイル用の std::filebuf の両方を試しています。 このようなことができるのは、フォーマットクラスが受け取るのが std::streambuf へのポインタだからです。 std::strstreambuf と std::filebuf は std::streambuf の派生クラスなので、どちらもフォーマットクラスと結びつけることができるのです。 |
|
この例からも分かるとおり、フォーマットクラスはバッファクラスから独立しています。対象への操作はバッファクラスに任せて、自分は << や >> を使った「インターフェイス」としての機能、そして文字列形式との適切な変換を行うという役に徹しているわけです。
ストリームクラスは便利に使えるよう、フォーマットクラスとバッファクラスをひとつのクラスに封じ込めています。正確に言うと「ストリームクラスはフォーマットクラスの継承クラスであり、バッファクラスをメンバ変数として持っている」のです。例えばこんな感じに。 //////////////////////////////////////////////////////////////// // ニセ std::strstream クラスです。 #include <stdio.h> #include <iostream> #include <strstream> // ストリームクラス class CFake_strstream : public std::iostream // フォーマットクラスから派生。 { std::strstreambuf m_cStrStrBuf; //バッファクラス。 typedef std::iostream type_Parent; // VC の謎エラー対策。 public: CFake_strstream( char *p_pchStr, std::streamsize p_iSize ) : m_cStrStrBuf( p_pchStr, p_iSize, p_pchStr ) , type_Parent( &m_cStrStrBuf ) {} }; // 使用例。 void Use_CFake_strstream() { char ch[130]; CFake_strstream cStrStrm( ch, 128 ); // ニセ std::strstream 。 cStrStrm << "Use_CFake_strstream()" << std::endl << std::ends; printf( "%s", ch ); } // 出力結果。 Use_CFake_strstream() ///////////////////////////// ニセ std::strstream クラスの CFake_strstream は、 std::iostream から継承して、バッファクラス std::strstreambuf をメンバ変数として持っています。そしてコンストラクタの中で適切に初期化しています。たったこれだけでほとんど std::strstream と同様の機能を持つクラスになります。 |
|
ちなみになぜ「標準入出力」、つまり std::cout と std::cin について触れないかというと、「標準入出力」自体は OS に密接に関わっているため、ライブラリごとに実装方法が違うからです。あるライブラリは、専用のバッファクラス std::stdiobuf が用意されています。また他のライブラリは std::filebuf を使って普通のファイルとして標準入出力を操作します。このように異なるため、とりあえず解説は避けることにします。
ただ std::cout が std::ostream の、 std::cin が std::istream の変数だということは確実なので、操作するときにはこれを踏まえていれば大丈夫でしょう。 |
(C)KAB-studio 2001 ALL RIGHTS RESERVED. |