株式会社エス・スリー・フォー

9章: クラス RWBTreeOnDisk の使い方

続きを見る

18.5 多重継承

続きを見る

18.4 RWCollectable の格納と取り出しについて

続きを見る

6.6 まとめ

続きを見る

6.4 DDE の例

続きを見る

6.3 Windows クリップボードと DDE Streambuf

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

streambuf クラスは、iostream 機能の基礎となるシーケンス層です。これは、文字の順序の生成と利用を行います。コンパイラに付属のものには、いくつかの種類の streambuf があります。例えば、filebuf クラスは最終的に文字をファイルに入力および出力します。strstreambuf クラスは、メモリベースの文字ストリームで入力および出力をします。つまり、ANSI-C の sprintf() 関数と同等の iostream と考えることができます。今回、Tools.h++ には次の Windows ベースの拡張機能が付け加えられました。

  • Windows クリップボードでの入力および出力を行うための RWCLIPstreambuf クラス
  • Windows の DDE (動的データ交換) 機能を経由して入力および出力を行うためのRWDDEstreambuf クラス

これらのクラスは、バッファがオーバーフローまたはアンダーフローすると、Windows からのメモリの割り当てと解放を行います。RWDDEstreambuf クラスの場合は、関連する DDEDATA ヘッダも自動的に書き込まれます。Rogue Wave の仮想ストリームクラスと同様に、istreamostream を含め、ios クラスを継承したすべてのクラスはこれらの streambuf と共に使用できます。

例えば、複雑な構造を一般のディスクベースのファイルに格納するためのコードは、DDE 機能でその構造を別のアプリケーションに転送するためにも使用できるということです。

6.2 簡単な例

次に、抽象基底クラス RWvostreamRWvistream を通して、RWbostreamRWbistream を用いる簡単な例を示します。

#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(const RWCString& a, RWvostream& v) の役目は、文字列 a を仮想出力ストリーム v に保存することです。関数 recover(RWvistream&) はその結果を読み出します。これらの関数は、文字列の格納に使用される最終形式には関知しません。次に各行に対する注釈を示します。

//1-//2
この 2 行では、ファイル junk.dat に対してファイル出力ストリーム f が作成されます。多くの PC コンパイラのファイルオープン時のデフォルトモードは text であるため、DOS の自動改行変換を避けるために明示的にフラッグ ios::binary を使用しなければなりません。[1]
//3
この行で、f から RWbostream が作成されます。
//4
この文は { … } で囲まれているので、f のデストラクタがここで呼び出されます。これにより、ファイルを閉じます。
//5
ファイルを再び入力のために開きます。
//6
f から RWbistream が作成されます。
//7
文字列がファイルから読み出されます。
//8
最後に、オリジナルの文字列と読み出された文字列を比較のために出力します。

このプログラムは、fstream クラスを使って簡略化することができます。fstream クラスは、出力と入力のために ofstreamifstream を多重継承します。結果を読み込む前に、ファイルの先頭を見つける必要があります。seekg() の早期実装は、信頼性が証明されていないので、上記例ではこの手段がとられていません。

注釈

  1. ^ 多くの PC コンパイラでは、 ios::binary フラッグ付きでファイルが開かれていないかぎり、ostream::write() および istream::read() はテキスト変換を実行します。

6.1 特殊仮想ストリーム

Rogue Wave クラスには、RWvistreamRwvostream を特殊化した 4 つのクラスがあります。最初のクラスは移植可能な ASCII 形式、2 番目と 3 番目のクラスはバイナリ形式、4 番目のクラスは XDR 形式 (eXternal Data Representation の略で Sun Microsytems の規格) を使用します。

 
入力クラス
出力クラス
抽象基底クラス
RWvistream
RWvostream
移植可能 ASCII
RWpistream
RWpostream
バイナリ
RWbistream
RWbostream
エンディアン
RWeistream
RWeostream
XDR
RWXDRistream
RWXDRostream

形式で挿入された項目を格納します。これにより、異なるオペレーティングシステム環境でも、挿入項目が正しく復元されます。バイナリ用のクラスは、挿入項目を再フォーマットすることなく、ネイティブ形式で格納します。エンディアン用のクラスは、バイナリ形式にすることによって、容量の縮小や処理時間の短縮を行い、情報をビッグエンディアン、リトルエンディアンあるいはネイティブ形式で格納または取り出します。XDR バージョンは、ネットワーク経由で遠隔に転送するために、情報を XDR ストリームに送ります。

これらのクラスはいずれも、どんな状態も保持しないため、 XDR を含め、これらのバージョンを一般のストリームと自由に置き換えることができます。これらを使用しても、ファイル I/O すべてをこれらのクラスで行わなければならないわけではありません。詳しくは、『Tools.h++ Class Reference』の関連項目を参照してください。

5.1 時間帯の設定

問題は、ライブラリがどのようにこのローカル時刻を判断するかです。

UNIX オペレーティングシステムでは、ローカル時間帯の設定および夏時間が実施されるかどうかが提供されます。RWTime クラスはさまざまなシステム呼び出しを使用して、これらの値を判断し、それに従って設定します。RWTime クラスは、北米または夏時間が実施されない場所では正しく機能するはずです。米国の夏時間の規則に準じていない場所では、ローカル時間帯を初期化する必要がある場合があります。RWZone については、『Tools.h++ Class Reference』を参照してください。

各種の Windows オペレーティングシステムのユーザは、時刻を手動で切り替える必要があるかもしれません。これをどのようにするかは、使用コンパイラによって異なります。何も行なわない場合、クラスはローカル時刻の場合には正しく機能しますが、コンピュータにローカル時刻から GMT へのオフセットを知る手段がないため、正しい GMT が計算できない可能性があります。

Borland、MetaWare、Microsoft、Symantec、Watcom のいずれかのコンパイラを使用している場合は、環境変数 TZ を適切な時間帯に設定する必要があります。例:

set TZ=PST8PDT

詳しくは、使用コンパイラの実行時のライブラリリファレンスで関数 tzset() または _tzset() の説明を参照してください。

また、使用コンピュータのシステムクロックが正しく設定され、動作していることが不可欠です。PC を使用している場合は、システムクロックのバッテリが充電されていることを確認してください。

18.3 RWStringID

Rogue Wave ユーザから、RWClassID で可能な範囲よりも大きな範囲のクラス ID を、RWCollectable クラスで使えるようにしてくれないかという要請がたくさんありました。そこで、既存の多形永続性を持つファイルの逆互換性を保つために、RWClassID の意味を変更せずに、新しい種類のクラス ID である RWStringID を追加しました。

RWStringID は、次のいずれかの方法で RWCollectable クラスに関連付けることができます。そのクラスの RWStringID を選択するか、あるいはライブラリに自動的に RWStringID を生成させるかのどちらかの方法を取ります。自動的に生成した場合は、例えば、class MyColl のようにクラス名と同じ文字列になります。public RWCollectable によって自動的に RWStringID “MyColl” を取得できます。

固定された RWClassID と、以下のようにマクロ RWDEFINE_COLLECTABLE で生成された RWStringID でクラスを指定します。

RWDEFINE_COLLECTABLE(ClassName, ClassID)
RWDEFINE_COLLECTABLE(MyCollectable1,0x1000)         //例

固定された RWStringID と、次のようにマクロ RWDEFINE_NAMED_COLLECTABLE で 生成された RWClassID でクラスを指定します。

RWDEFINE_NAMED_COLLECTABLE(ClassName, StringID)
RWDEFINE_NAMED_COLLECTABLE(MyCollectable2, "Second Collectable") // 例

上記例を使って、次にように書くことができます。

// 最初に試行を設定する
MyCollectable1 one; MyCollectable2 two;
// 実行中のすべての RWClassID は異なることが保証される
one.isA() != two.isA();
// 各 RWCollectable が RWStringID を持つ
one.stringID() == "MyCollectable1";
// ID を見つけるには、いくつかの方法がある
RWCollectable::stringID(0x1000) == "MyCollectable1";
two.isA() == RWCollectable::classID("Second Collectable");

18.3.1 識別子の寿命

同じ、または異なるプログラムの各実行ごとに多形永続性を与えるには、永続性を持たせる各クラスに永久の識別子が必要です。これまで、どんな RWCollectable でも永久の識別子は、その RWClassID を持っていました。RWCollectable から派生した各クラスごとに、RWDEFINE_COLLECTABLE によってクラスとその RWClassID を永久に関連付けるコードが生成されました。この識別方法は、そのまま存在しますが、現在のバージョンの Tools.h++ では、マクロ RWDEFINE_NAMED_COLLECTABLE を使って、選択した RWStringID をクラスに永久に連結することができます。

追加の RWStringID 識別子により、前の制約の元で可能だったものより、さらに ID が増え、RWCollectable にさらに説明的な ID を付けることが可能になりました。新しい識別子を取り入れるために、テンポラリの RWClassID は、デベロッパによって指定されたRWStringID を持つ各 RWCollectable クラスごとに生成されるようになりました。これらの RWClassID は、実行ファイルの実行中に必要に応じて構築され、その間は定数として留まります。ただし、これらの識別子は、別の実行ファイルあるいは別の実行中には異なる順序で生成されるので、永久的に格納するには適していません。

18.3.2 RWStringID でのプログラミング

RWCollectable には、新しい通常メンバ関数が 1 つ、静的メンバ関数が 2 つ加わりました。バージョン 6 に対してコンパイルされたオブジェクトとの互換性を保つことが、バージョン 7 Tools.h++ の主な目標の 1 つなので、これらの関数はいずれも仮想関数ではありません。そのため、リンクの互換性を無視した場合に比べて、これらの関数はやや性能が劣ります。

新しい通常メンバ関数は、次のとおりです。

RWStringID  stringID() const;

The new static member functions are:

RWStringID  stringID(RWClassID);           // RWStringID をルックアップする
RWClassID  classID(RWStringID);              // ClassID をルックアップする

RWFactory にも、次の新しい関数が含まれています。

void           addFunction(RWuserCreator, RWClassID, RWStringID);
RWCollectable* create(RWStringID) const;
RWuserCreator  getFunction(RWStringID) const;
void           removeFunction(RWStringID);
RWStringID     stringID(RWClassID) const;
RWClassID      classID(RWStringID) const;

Tools.h++ と一緒に出荷される RWCollectable と、前のバージョンとまったく同じように固定された RWClassID で定義されている RWCollectable を使うことができます。例えば、次の共通プログラミング記法を使うことができます。

RWCollectable *ctp;                         // ポインタを指定
if (ctp->isA() == SOME_CONST_CLASSID)      // 特定のことを行う

ただし、ユーザ定義の RWStringID (つまり、非永久的な ClassID) を持つ RWCollectable を使う場合は、実行ファイルを実行するごとに RWClassID の値が異なることに注意してください。これらのクラスの場合、上の記法を置き換えることのできる記法が 2 つあります。

RWCollectable *ctp;
// ポインタを指定
// 比較に既存の RWCollectable を使う:
// RWStringID を比較するよりも、比較が高速になる
if(ctp->isA() == someRWCollectablePtr->isA())
  // そのクラスインタフェースにコードを書くことができる
// ...
  // 同定をハードコードするための記法。文字列比較は
  // 整数比較よりも遅いので、やや遅くなる。
  // また、stringID() は辞書のルックアップを使う。
if (ctp->stringID() == "Some ID String")  {
  // そのクラスインタフェースにコードを書くことができる

18.3.3 RWStringID の実装詳細

18.3.3.1 節から 18.3.3.5 節では、RWStringID の実装の詳細を説明します。仮想メソッドを使わずにどのようにして仮想関数の機能を追加するのかに関心がある場合、設計、効率性、およびその他の特定の問題に興味がある場合には、これらの節を読んでください。

18.3.3.1 自動 RWClassID

自動 RWClassID は、0×9200 〜 0xDAFF の範囲で未使用の RWClassID からシステム化された方法で作成されます。作成可能な RWClassID は 18,687 個あるので、極端なプログラムでないかぎり、RWClassID を使い切ることはまずありません。ただし、並外れたユーザに対応するために、あえてここで警告しておきます。自動的に生成された RWClassID を持つクラスは、1 つのプログラムで、18,688 個以上作成または使用することはできません。[1].

ただし、各クラスが持つことができる合計オブジェクト数とは何も関係ないことに注意してください。各クラスの合計オブジェクト数は、オペレーティングシステムとコンパイラの条件によってのみ制約されています。もちろん、0×8000 未満の完全なセットの RWClassID にもアクセスできます。さらに 32,767 個の RWCollectable を作成できますが、これらの ID は自動的には生成されないため、手動操作で指定しなければなりません。

18.3.3.2 静的メソッドを使って仮想メソッドを実装する場合

isA() 仮想メソッドは、「実行時に一意な」RWClassID を返すので、この 1 つの仮想メソッドを用いて各種のデータや関数ポインタが格納されているルックアップ表にインデックスを与えることができます (C++ に内蔵されている vtables を連想するかもしれません)。RWCollectable はすでに単一の RWFactory の存在に依存しているため、RWFactory インスタンスを使ってルックアップ情報を保管することにしました。

次の静的メソッドは、

RWStringID  RWCollectable::stringID(RWClassID id);

RWFactory インスタンスで id をルックアップしようとします。関連付けられた RWStringID を見つけた場合は、それを返します。RWStringID が見つからなかった場合は、RWStringID(“NoID”) を返します。

次の静的メソッドは、

RWClassID  RWCollectable::classID(RWStringID sid)

RWFactory インスタンスで sid と関連付けられている RWClassID があるかどうかを調べて、アナログ的に機能します。見つかった場合は、それを返します。見つからなかった場合は、RWClassID __RWUNKNOWN を返します。

18.3.3.3 多形永続性

RWCollectable の多形永続性は、新しい RWStringID クラスによっては影響を受けません。旧 RWClassID が変更されないかぎり、既存のファイルは、新しくコンパイルされリンクされた実行可能ファイルを使って、そのまま読み込むことができます。RWStringID を持つ新しいクラスは、古いクラスと自由に混在させることができます。永久的な RWClassID を持たない RWCollectable の格納サイズには、より大きな領域が必要となりますが、これによって他の RWCollectable の格納サイズが影響を受けることはありません。

同一 RWStringID を持つ RWCollectable が含まれているコレクションでは、同一 RWCollectable への複数参照でそれが最初に現われたときにだけ格納されるように、その RWStringID がストリームまたはファイルに一度だけ格納されます。

18.3.3.4 効率性

RWClassID は、速度とメモリ領域の両方において RWStringID よりも効率がいいため、できるかぎり引き続き RWClassID を使うことをお勧めします。RWStringID は、次の点で便利です。

  • 整理上多くのプログラミンググループに一意の ID を作成する必要がある場合
  • サードパーティライブラリで他のライブラリやユーザとの衝突を避ける必要がある場合
  • 多少効率が悪くなっても RWStringID の記述機能が意味をなす場合

RWStringID は、現在バージョンの Tools.h++ でコンパイルされる RWCollectableクラスすべてに作成されます。この追加のコードは、RWStringID を使用しないプログラムに対してそれほど大きな影響を与えることはありません。RWClassIDRWStringID からのルックアップを保管するために RWFactory がより大きくなり、追加のデータを RWFactory に取り入れるために起動時間はやや長くなります。

18.3.3.5 識別子の衝突

RWStringID により識別子の衝突が緩和されますが、異なるクラスの RWStringID 間で衝突の可能性がまだ存在します。次の場合に、衝突が発生する可能性があります。

  • 自動的に生成された RWStringID が、ユーザの選択した RWStringID と競合する場合
  • 複数のクラスに間違って同一の RWStringID が指定された場合
  • 異なる名前空間にある 2 つのクラスが同じ名前を持ち、そのため自動的に同じ RWStringID が生成された場合。これは、使用コンパイラが名前空間に対応していると仮定しています。

場合によっては、これらの衝突は重要ではありません。自動的に生成された RWClassID は、互いに異なること、および正規のユーザ指定の RWClassID とは異なることが保証されています。isA() と stringID() の各仮想メソッドおよびコンストラクタの RWClassID を基準にしたルックアップは、すべて引き続き正しく機能します。

ただし、衝突が問題が引き起こすような場合がいくつかあります。衝突するようなユーザ選択の RWStringID を持つクラスの多形永続性は、正しく機能しません。これらの場合、正しく格納されているとしても、データは回復不能です。同様に、RWStringID だけでクラス間を区別するようなユーザのコードは失敗します。

このような衝突は、避けることができます。まず最初に、他と衝突することはまずない RWStringID を使用すべきです。例えば、クラスの継承階層を表わす、またはリビジョン管理システムで使用されているように開発者名や会社名、作成時間、ファイルパスを埋め込んだ、RWStringID を使用します。そして、常に作成したプログラムをテストして、実際に RWStringID に関連付けられているクラスが期待したものであることを確認してください。

注釈

  1. ^ 16 ビットの DLL がメモリ上に読み込まれるときも、自動的に RWClassID が生成されます。