Previous Next Contents Index


Copyright 1999 Rogue Wave Software
Copyright 1999 Sun Microsystems, Inc.

仮想ストリーム

5


iostream 機能は、すべての C++ コンパイラに付属しているもので、C++ プログラマがよく使用する基本機能です。その利点としては、型保証されたデータをストリームに挿入したり、ストリームから抽出したりすることや、新しい型への拡張性などがあります。また、クラス streambuf によって設定される、ストリームバイトの送信元と受信先のユーザーに対して完全に透過的であるという利点もあります。

しかし、iostream 機能には制限も多数あります。特に、書式指定機能が劣ります。たとえば、doubleostream に挿入する場合、それをバイナリとして挿入するための型保証の方法がありません。さらに、すべてのバイトの送信元と受信先が streambuf モデルに適合しているわけではありません。XDR など多くのプロトコルでは、フォーマットはバイトストリームに組み込み式で結合され、分離することができません。

Rogue Wave の仮想ストリームは、理想的なストリームモデルを提供することによって、これらの制限を克服しています。書式やストリームモデルについて、なんの仮定も設けていません。仮想ストリームのクラス階層の一番下にあるのはクラス RWvios です。これは抽象基底クラスのひとつで、標準ライブラリクラス ios とよく似たインタフェースを備えています。

class RWvios{
public:
  virtual int   eof()             = 0;
  virtual int   fail()            = 0;
  virtual int   bad()             = 0;
  virtual int   good()            = 0;
  virtual int   rdstate()         = 0;
  virtual int   clear(int v = 0)  = 0;
};
RWvios の特殊化版が、これらの関数の定義を提供します。

RWvios から継承されるのが抽象基底クラス RWvistream および RWvostream です。これらのクラスは、基本的な組み込み型および組み込み型の配列のすべてについて一式の純粋な仮想関数 (operator<<()put()get() など) を宣言します。

class RWvistream : public RWvios {
public:
  virtual Rwvistream&  operator>>(char&)       = 0;
  virtual Rwvistream&  operator>>(double&)     = 0;
  virtual int          get()                   = 0;
  virtual Rwvistream&  get(char&)              = 0;
  virtual Rwvistream&  get(double&)            = 0;
  virtual Rwvistream&  get(char*, size_t N)    = 0;
  virtual Rwvistream&  get(double*, size_t N)  = 0;
  .
  .
  .
};

class RWvostream : public RWvios {
public:
  virtual Rwvostream&  operator<<(char)             = 0;
  virtual Rwvostream&  operator<<(double)           = 0;
  virtual Rwvostream&  put(char)                    = 0;
  virtual Rwvostream&  put(double)                  = 0;
  virtual Rwvostream&  put(const char*, size_t N)   = 0;
  virtual Rwvostream&  put(const double*, size_t N) = 0;
  .
  .
  .
};
RWvistreamRWvostream から継承されるストリームは、これらの組み込み型や組み込み型の配列を、そのクラスのユーザーに対して透過的な形式で特殊化ストリームに格納します。

仮想ストリーム機能の基本的な抽象作用は、書式に関係なく組み込みが仮想出力ストリームに "挿入され"、仮想入力ストリームから "抽出される" ということです。つまり、出力に空白やコンマをパディングしたり、あるいはその他のフォーマットをいっさい行う必要がないということです。ユーザーは RWvostream に対して、実質的には「ここに double があります。これを何か都合のよい形式で格納してください。そして私がそれを求めたら適切な形で返してください」といった具合に依頼できるのです。

この結果はきわめて強力です。最終的な出力媒体や使用する形式について何も知らなくても、ストリーム演算子を使用したり、記述することができるからです。出力媒体としてはディスク、メモリー割り当て、あるいはネットワークが考えられます。また形式としては、バイナリ、ASCII 、あるいはネットワークパケットが考えられます。これらのすべてのケースについて、同じストリーム演算子を使用できます。


仮想ストリームの特殊化

Rogue Wave のクラスには、RWvistream および RWvostream を特殊化する 4 種類のクラスが含まれています。それらのクラスは可搬な ASCII 形式、バイナリ形式、エンディアン形式、XDR 形式をそれぞれ使用します。1

  Input class Output class
抽象基底クラス RWvistream RWvostream
可搬 ASCII RWpistream RWpostream
バイナリ RWbistream RWbostream
エンディアン RWeistream RWeostream
XDR RWXDRistream RWXDRostream

可搬な ASCII 形式を使用するクラスは、新しいオペレーティングシステムの下でも正しく復元されるように、タブや復帰改行などの特殊文字をエスケープする ASCII 形式で挿入項目を格納します。バイナリ形式を使用するクラスは、挿入項目を再フォーマットせず、それらの固有の形式で格納します。エンディアンバージョンを使用すると、バイナリ形式の領域の効率と時間的な効率がよくなり、さらに、ビッグエンディアン、リトルエンディアン、ネイティブのどの形式でも情報を格納したり取り出したりできます。XDR 形式を使用するクラスは、項目を XDR ストリームに送り、ネットワークを通じて遠隔地に伝送します。

これらのクラスはいずれも状態を保持しないので、通常のストリーム (XDR も含む) と自由に切り替えて利用できます。これらのクラスを使用するからといって、すべてのファイル入出力をそれぞれの形式に限定するというわけではありません。詳細は、『Tools.h++ 7.0 クラスライブラリ・リファレンスマニュアル』を参照してください。


簡単な例

以下に、RWbostream RWbistream を、それぞれの抽象基底クラスである RWvostreamRWvistream を通じて実行する簡単な例を示します。

#include <rw/bstream.h>
#include <rw/cstring.h>
#include <fstream.h>

#ifdef __BORLANDC__
# define MODE ios::binary                                        // 1
#else
# define MODE 0
#endif

void save(const RWCString& a, RWvostream& v){
  v << a;                    // 仮想出力ストリームに保存する
}

RWCString recover(RWvistream& v) {
   RWCString dupe;
   v >> dupe;            // 仮想入力ストリームから復元する
   return dupe;
}

main(){
   RWCString a("A string with\ttabs and a\nnewline.");

   {
     ofstream f("junk.dat", ios::out|MODE);                      // 2
     RWbostream bostr(f);                                        // 3
     save(a, bostr);
   }                                                             // 4

   ifstream f("junk.dat", ios::in|MODE);                         // 5
   RWbistream bistr(f);                                          // 6
   RWCString b = recover(bistr);                                 // 7

   cout << a << endl;  // 2 つの文字列を比較する                 // 8
   cout << b << endl;
   return 0;
}

プログラムからの出力

A string with   tabs and a
newline.
A string with   tabs and a
newline.

関数 save(RWCString& a, RWvostream& v) の仕事は、文字列 a を仮想出力ストリーム v に保存することです。関数 recover(RWvistream&) は結果を復元します。これらの関数は、文字列が最終的にどの形式で格納されるのか知りません。以下に、個々の行に関して説明します。

//1, //2 これらの行では、ファイル出力ストリーム f がファイル junk.dat に対して作成されます。多くの PC コンパイラのデフォルトファイルオープンモードは text であり、DOS(Windows を含む) の自動復帰改行変換を避けるために、明示フラグの ios::binary を使用する必要があります。1
//3 この行では、f から RWbostream が作成されます。
//4 この句は中括弧 {...} で囲まれているので、f のデストラクタがここで呼び出されます。これによって、このファイルがクローズされます。
//5 このファイルが再オープンされます (今回は入力用)。
//6 このファイルから今度は RWbistream が作成されます。
//7 このファイルから文字列が復元されます。
//8 最後に、元の文字列と復元された文字列の両方が比較のために出力されます。
1 多くの PC コンパイラでは、ios::binary フラグによってファイルがオープンされていないかぎり、ostream::write()istream::read() もテキスト変換を実行します。

クラス fstream を使用すると、このプログラムを簡略化することができます。このクラスは、出力と入力の両方で ofstreamifstream を多重継承します。ファイルの始まりのシークは、結果を読み戻す前に起こります。seekg() の初期の実装には、信頼性の低いものがいくつかあるため、この例では、簡単な方法を選択していません。


Windows のクリップボードと DDE Streambufs

前の節では、ストリームに挿入された項目の書式を仮想ストリーム機能がどのように抽象化するかを説明しました。ストリームに挿入された項目の設定も抽象化されています。これは、使用される streambuf の型によって設定されます。

クラス streambuf は、iostreams 機能の基本となる順序付け層です。このクラスは、文字シーケンスを作成して消費する義務があります。コンパイラにはいくつかのバージョンがあります。たとえば、クラス filebuf は、最終的にその文字を獲得してファイルに挿入します。クラス strstreambuf は、文字を獲得してメモリーベースの文字ストリームに挿入します。これは、ANSI C の sprintf() 関数に相当する iostream の機能と考えることができます。Tools.h++ には、次の 2 つの Windows ベースの拡張機能があります。

これらのクラスは、バッファがオーバーフローしたりアンダーフローしたりするときに、Windows からのメモリーの割り当てと開放の細部を処理します。クラス RWDDEstreambuf の場合は、関連する DDEDATA ヘッダも自動的に埋められます。クラス ios から継承するクラスはすべて、これらの streambufs で使用することができます。これには、Rogue Wave の仮想ストリームクラスだけでなく、既知の istreamostream も含まれます。

この結果、たとえば、複雑な構造を従来のディスクベースのファイルに保管するときに使用するものと同じコードを使用して、DDE 機能によりその構造を別のアプリケーションに転送することなどができます。


DDE の例

クラス RWDDEstreambuf を使用して、Windows DDE 機能によって RWBinaryTree を交換する方法を示すより複雑な例を次に示します。Windows のクリップボードの場合も、同様に技術を使用します。

#include <rw/bintree.h>
#include <rw/collstr.h>
#include <rw/bstream.h>
#include <rw/winstrea.h>
#include <windows.h>
#include <dde.h>

BOOL
PostCollection(HWND hwndServer, WORD cFormat){
   RWBinaryTree sc;                                          // 1
   sc.insert(new RWCollectableString("Mary"));
   sc.insert(new RWCollectableString("Bill"));
   sc.insert(new RWCollectableString("Pierre"));

   // RWDDEstreambuf を割り当て、それを使用して
   // RWbostream を初期化する
   RWDDEstreambuf* sbuf = new RWDDEstreambuf(cFormat,        // 2
                                             FALSE,          // 3
                                             TRUE,           // 4
                                             TRUE);          // 5
   RWbostream bostr( sbuf );                                 // 6

   // コレクションを RWbostream に格納する
   bostr << sc;                                              // 7

   // 出力ストリームをロックして、そのハンドルを獲得する
   HANDLE hDDEData = sbuf->str();                            // 8

   // アトムを獲得して、DDE メッセージを識別する
   ATOM atom = GlobalAddAtom("SortedNames");                 // 9

   // DDE 応答を送信する
   return PostMessage(0xFFFF, WM_DDE_DATA, hwndServer,       //10
                      MAKELONG(hDDEData, atom));
}

前述のコードでは、ラージメモリーモデルが想定されています。次に、行単位の説明を示します。

//1 RWBinaryTree が作成されて、そこにいくつかの項目が挿入されます。
//2//5 RWDDEstreambuf が割り当てられます。コンストラクタは、いくつかの引数をとります。最初の引数は、Windows のクリップボード形式です。この例では、形式の型が引数として渡されていますが、通常は、Windows の RegisterClipboardFormat() を使用して形式を登録し、その形式を使用します。

その他の引数は、DDE データ交換の応答とメモリー管理の複雑さと関係があります。引数のリストについては、『Tools.h++ 7.0 クラスライブラリ・リファレンスマニュアル』を参照してください。また、その意味については、『Petzold (1990), Chapter 17』か『Microsoft Windows Guide to Programming』を参照してください。

//6 RWbostream が、提供されている RWDDEstreambuf から作成されます。ここで、RWpostream を使用することもできますが、DDE 交換は同じマシンアーキテクチャ内で実行されるため、可搬 ASCII 形式を使用して余分なオーバーヘッドをかける価値はありません。しかし、streambuf の型によって行われるバイト列の配置方法が、RWvostream の型によって設定されるその書式とどれぐらいはっきりと分離されるかには注意してください。
//7 コレクションが RWbostream に格納されます。RWbostream に関連する streambuf は実際には RWDDEstreambuf であるため、コレクションは実際には、特性 GMEM_DDESHARE を持つ Windows の大域メモリー割り当てに格納されます。この割り当ては、オーバーフローすると自動的にサイズ変更されます。他の strstreambuf と同様、割り当てチャンクのサイズは、メンバー関数 setbuf() を使用して変更することができます。
//8 RWDDEstreambuf がロックされます。この streambuf は、str() を使用してロックされると、他の strstreambuf と同様、再使用することができません。ただし、RWDDEstreambuf::str() は、char* ではなくハンドルを返すことに注意してください。ハンドルは、ロック解除されてから返されます。
//9 アトムが作成されてこの DDE データを識別します。
//10 RWDDEstreambuf::str() によって返されるハンドルが、その識別アトムとともに送信されます。

クリップボードの交換では、実際にはこれよりも簡単な類似の技法を使用することができます。

特殊な streambufs である RWCLIPstreambufRWDDEstreambuf を Rogue Wave 仮想ストリーム機能だけで使用する場合、制限は何もないことに注意してください。これらは、通常の istreamsostreams であれば、非常に簡単に使用することができます。実行時にフォーマットを設定できないだけです。


RWAuditStreamBuffer

クラス RWDDEstreambufRWCLIPstreambuf は、streambuf を特殊化し、Windows API に従って文字を渡します。ただし、streambuf には、他に役立つ特殊化があります。クラス RWAuditStreamBuffer を使用すると、各文字に選択した関数を任意で呼び出している間、すべてのストリームのバイトをカウントすることができます。『Tools.h++ 7.0 クラスライブラリ・リファレンスマニュアル』のコードの例を参照してください。


まとめ

この章では、ストリームのバイト列の最終的な宛先を考慮せずに、どのようにオブジェクトをストリームに格納し、またストリームから復元するかについて学習しました。宛先はメモリー内であっても、ディスク上であってもかまいません。また、ストリームの最終的な書式を考慮する必要がないということも学習しました。書式は ASCII でもバイナリでもかまいません。

ユーザーが RWpostreamRWpistream のように特殊化された "仮想ストリーム" クラスを独自に記述することも可能です。仮想ストリーム機能の最大の利点は、ユーザーが独自の特殊化仮想ストリームを書くときに、クライアントクラスのコードをまったく修正する必要がないということです。目的のストリームクラスを、以下に示すものへの引数として使用するだけでよいのです。

RWvostream& operator<<(RWvostream&, const ClassName&);
RWvistream& operator>>(RWvistream&,       ClassName&);
仮想ストリームに対するオブジェクトの記憶と取り出しに加え、すべてのクラスがそれ自身をバイナリ形式で RWFile に格納したり、そこから取り出すことができます。このファイルは ANSI-C スタイルのファイル入出力をカプセル化します。この形式の記憶と取り出しはストリーム入出力よりも機能上の制限がありますが、仮想ディスパッチ機構が必要ないのでディスクとの間では多少高速に行われます。



1 XDR は eXternal Data Representation の略で、サン・マイクロシステムズの標準の 1 つです。


Previous Next Contents Index