Copyright 1999 Rogue Wave Software
Copyright 1999 Sun Microsystems, Inc.
RWCollectable クラスの設計 |
14 |
この節では、多相持続性機構自体については説明しません。多相、同形、単純の各持続性については前の章の「持続性」で詳しく説明してあります。要約すると、多相持続性とは、ストリームまたはファイルとの間で、持続オブジェクト間のポインタの関係が維持されるような方法でオブジェクトを格納して取り出し、それらのオブジェクトの型を知らなくても復元できる機能のことをいいます。
RWCollection クラスを設計する理由
RWCollectable クラスの実際の設計方法を説明する前に、RWCollectable クラスを設計する理由について具体的な例を示します。
ユーザーがバス会社を経営していると想定します。乗務追跡システムの一部を自動化するために、バス、バスの顧客の集合、実際の乗客の集合を表わすクラスを作成する必要があります。ある人間が乗客になるためには、まず顧客にならなければなりません。したがって、顧客の集合は、乗客の集合の上位セットになります。また、1 人の人間は物理的に特定のバスに 1 回しか乗ることができないため、同じ人間を顧客リストに 2 回以上記載しても意味はありません。このシステムの開発者は、どちらのリストにも重複がないように注意する必要があります。
もし重複があると問題が起こる可能性があります。プログラムで、バスとその顧客に関する情報を保存して復元しなければならない場合を考えてください。バスを多相保存する必要があるとき、プログラムが顧客の集合に続いて、乗客の集合を単純に反復してそれぞれ 1 回ずつ保存すると、顧客でも乗客でもある人間が 2 回保存されてしまいます。プログラムがそのバスを多相復元した場合、乗客のリストはすでに顧客リスト内にある人間を単純には参照しません。それどころか、各乗客が両方のリストに別々のインスタンスを持つことになります。
ある人間がすでにストリームに多相保存されていて、その人間をもう一度保存するのではなく、前のインスタンスへの参照だけを保存すべきときを認識する方法が必要です。
これが、クラス RWCollectable の仕事です。RWCollectable から継承するオブジェクトは、その内容だけではなく、RWCollectable から継承する他のオブジェクトとの関係も保存することができます。この機能を同形持続性と呼びます。クラス RWCollectable には同形持続性がありますが、それだけではなく、保存または復元されるオブジェクトの型を実行時に判別することができます。RWCollectable によって提供される型の持続性を多相持続性と呼び、同形持続性の上位セットと考えます。
RWCollectable クラスの例
次のコードは、前の節で説明したクラスをどのように宣言するかを示しています。その後で、マクロ RWDECLARE_COLLECTABLE を使用して、この機能を使う理由を説明します。この例の元となった完全なコードは、この例の章の最後に示してあります。この例のファイルは、toolexam ディレクトリの中に Bus という名前であります。
class Bus : public RWCollectable { RWDECLARE_COLLECTABLE(Bus) public: Bus(); Bus(int busno, const RWCString& driver); ~Bus(); // クラス 「RWCollectable」から継承 Rwspace binaryStoreSize() const; int compareTo(const RWCollectable*) const; RWBoolean isEqual(const RWCollectable*) const; unsigned hash() const; void restoreGuts(RWFile&); void restoreGuts(RWvistream&); void saveGuts(RWFile&) const; void saveGuts(RWvostream&) const; void addPassenger(const char* name); void addCustomer(const char* name); size_t customers() const; size_t passengers() const; RWCString driver() const {return driver_;} int number() const {return busNumber_;} private: RWSet customers_; RWSet* passengers_; int busNumber_; RWCString driver_; }; class Client : public RWCollectable { RWDECLARE_COLLECTABLE(Client) Client(const char* name) : name_(name) {} private: RWCString name_; // この例に関する他のクライアント情報を無視する }; |
両方のクラスが RWCollectable からどのように継承しているかに注意してください。項目の重複が認められないクラス RWSet を使用して、顧客の集合を実装することにしました。これは、同じ人間が顧客リストに 2 回以上記載されないよう保証します。同じ理由で、クラス RWSet を使用して、乗客の集合を実装することにしました。しかし、この集合をヒープ上でライブな状態にしました。これにより、次の説明でいくつかの点がわかりやすくなります。
RWCollectable オブジェクトの作成方法
次に、ユーザーのオブジェクトを RWCollectable から継承させる方法の概要を示します。
Int compareTo(const RWCollectable*) const; RWBoolean isEqual(const RWCollectable*) const; unsigned hash() const;
RWspace binaryStoreSize() const; void restoreGuts(RWFile&); void restoreGuts(RWvistream&); void saveGuts(RWFile&) const; void saveGuts(RWvostream&) const;
デフォルトコンストラクタの定義
すべての RWCollectable クラスにはデフォルトコンストラクタが必要です。デフォルトコンストラクタは引数をとりません。持続性機構はこのコンストラクタを使用して、空のオブジェクトを作成し、そのオブジェクトを適切な内容で復元します。
Bus::Bus() : busNumber_ (0), driver_ ("Unknown"), passengers_ (rwnil) { }
ユーザーのクラスにクラス識別子を指定する
多相持続性を使用すると、あるクラスを 1 つの実行可能プログラムに保存して、それを異なる実行可能プログラム、あるいは、元の実行可能プログラムの再度の実行で復元することができます。実行可能プログラムでの復元では、そのクラスの型を前もって知らなくても、そのクラスを使用することができます。多相持続性を可能にするために、固有で1、変更されることのない識別子がクラスに必要です。RWCollectable から派生したクラスは多相持続性があるため、このような識別子が必要です。
識別子は数字か文字列のどちらかです。数値識別子は RWClassID という型名に typedef された unsigned short 型です。文字列識別子は RWStringID という型名に typedef された型です。数値識別子を指定する場合、ユーザーのクラスには文字列識別子が自動的に生成されます。これは、クラスの名前と同じ順序の文字シーケンスになります。同様に文字列識別子を指定する場合も、クラスには、実行可能コードでの使用時に、数値 ID が自動的に生成されます。
Tools.h++ には、ユーザーが設計したクラスに識別子を指定するための定義マクロが 2 つあります。数値 ID を指定したい場合は、次のマクロを使用してください。
RWDEFINE_COLLECTABLE (className, numericID)
定義マクロは、クラスのヘッダファイルには入れないように注意してください。マクロは、クラスを使用する .cpp ファイルの一部になります。1 つのソースファイル (.cpp) だけにおいて、作成する各 RWCollectable クラスに対して、1 つの定義マクロを正しく含める必要があります。クラス名を最初の引数として、数値クラス ID か文字列クラス ID を 2 番目の引数として使用してください。バスの例では、次の定義マクロを含めることができます。RWDEFINE_NAMED_COLLECTABLE (className, stringID)
RWDEFINE_COLLECTABLE(Bus, 200)
RWDEFINE_NAMED_COLLECTABLE(Client, "a client")
このマニュアルの残りでは、RWDEFINITION_MACRO を使用して、どちらかのマクロも選択できることを示します。例のコードでは、どちらか一方のマクロをとります。
仮想関数 isA()
仮想関数 isA() はクラス識別子を返します。これは、オブジェクトのクラスを識別する固有の番号です。この識別子を使用すれば、オブジェクトが属するクラスを判別することができます。次に、マクロ RWDECLARE_COLLECTABLE によって提供される関数宣言を示します。
virtual RWClassID isA() const;
RWCollectableString yogi; yogi.isA() == __RWCOLLECTABLESTRING; // TRUE と評価
仮想関数 newSpecies()
この関数は、自分自身と同じ型の新規オブジェクトへのポインタを返します。次に、マクロ RWDECLARE_COLLECTABLE によって提供される関数宣言を示します。
virtual RWCollectable* newSpecies() const;定義は、どちらかの RWDEFINITION_MACRO によっても自動的に提供されます。
仮想関数の定義を追加する
クラス RWCollectable は、次の仮想関数を宣言します。
virtual ~RWCollectable(); virtual Rwspace binaryStoreSize() const; virtual int compareTo(const RWCollectable*) const; virtual unsigned hash() const; virtual RWClassID isA() const; virtual RWBoolean isEqual(const RWCollectable*) const; virtual RWCollectable* newSpecies() const; virtual void restoreGuts(RWvistream&); virtual void restoreGuts(RWFile&); virtual void saveGuts(RWvostream&) const; virtual void saveGuts(RWFile&) const;
これらの仮想関数については、いくつかに分けて説明します。デストラクタについては 345 ページの「オブジェクトの破壊」、関数 binaryStoreSize()、saveGuts()、restoreGuts() については 346 ページの「多相持続性の追加方法」で説明します。ここでは、クラスに持続性を追加する方法を説明します。仮想関数 isA() と newSpecies() はマクロによって宣言され定義されるため、前出の「仮想関数 isA()」と「仮想関数 newSpecies()」で説明しました。この節では、残りの関数 compareTo()、isEqual()、hash() について説明します。344 ページの「compareTo()、isEqual()、hash() の例」には、これらのすべての関数が同じデータをどう処理するかを示す非常に簡単な例を示してあります。
virtual int compareTo(const RWCollectable*) const;
あるオブジェクトが他のオブジェクトよりも大きいか小さいか、または等しいかの定義と意味は、クラス設計者に委ねられています。クラス RWCollectable にあるデフォルト定義では、オブジェクトの 2 つのアドレスが比較されます。このデフォルト定義は可変部分と見なす必要があります。実際には、これはあまり便利ではなく、プログラムを実行するたびに変わる可能性があります。
int Bus::compareTo(const RWCollectable* c) const { const Bus* b = (const Bus*)c; if (busNumber_ == b->busNumber_) return 0; return busNumber_ > b->busNumber_ ? 1 : -1; }
ここまでは、c が指す RWCollectable の実際の型を常に Bus と想定してきましたが、ここに危険があります。不注意なユーザーがたとえば、RWCollectableString をコレクションに挿入した場合、キャスト (const Bus*) c の結果は無効なので、その間接参照によって障害が起こるおそれがあります4。多重定義されたすべての仮想関数は、同じ識別記号を共有しなければならないため、最下位の共通部分 (各クラスが共通して継承している基底クラス。この場合はクラス RWCollectable) を返す必要があります。この結果、コンパイル時の型検査がほとんど失敗します。
注 - コレクションのメンバーが同種 (つまり、すべてが同じ型) であるか、またはそれらを区別する方法があるかに注意する必要があります。このためには、メンバー関数 isA() か stringID() を使用することができます。
RWBoolean isEqual(const RWCollectable* c) const;
isEqual を同値であると定義する必要はないことに注意してください。isEqual は、2 つのオブジェクトがある意味で等値であることを意味する場合があります。実際には、2 つのオブジェクトの型は同じでなくてもかまいません。唯一の要件は、引数として渡されるオブジェクトが型 RWCollectable を継承することだけです。ユーザーが、実行するすべての型キャストが適切であることを確認する必要があります。
Bus クラスに適切な isEqual の定義は次のようになります。
RWBoolean Bus::isEqual(const RWCollectable* c) const { const Bus* b = (const Bus*)c; return busNumber_ == b->busNumber_; }
unsigned hash() const;
unsigned Bus::hash() const{ return (unsigned)busNumber_; }
unsigned Bus::hash() const{ return driver_.hash(); }
注 - isEqual で TRUE と見なされる 2 つのオブジェクトが同じ値にハッシュされるものと想定します。
RWCollectableString a("a"); RWCollectableString b("b"); RWCollectableString a2("a"); a.compareTo(&b); // -1 を返す a.compareTo(&a2); // 0 を返す (「等しいとみなす」) b.compareTo(&a); // 1 を返す a.isEqual(&a2); // TRUE を返す a.isEqual(&b); // FALSE を返す a.hash() // 96 を返す (オペレーティングシステムによって異なる) |
RWCollectableStrings の compareTo() 関数が、文字列の大文字小文字を区別して字句どおりに比較するよう定義されていることに注意してください。詳細については、『Tools.h++ 7.0 クラスライブラリ・リファレンスマニュアル』のクラス RWCString を参照してください。
オブジェクトの破壊
クラス RWCollectable から継承されるすべてのオブジェクトは仮想デストラクタを継承します。したがって実行時まで、削除するためにそのオブジェクトの実際の型を知る必要はありません。これにより、コレクションのすべての項目を、その実際の型を知らずに削除することができます。
C++ のすべてのクラスと同様、RWCollectable から継承されるオブジェクトは、保持している資源を解放するためにデストラクタを必要とすることがあります。Bus の場合、乗客と顧客の名前はヒープから割り当てられた RWCollectableString であり、これらはヒープに返還する必要があります。これらの文字列はクラスの有効範囲外には決して現われないので、ユーザーによってアクセスされることはありません。したがって、デストラクタの中で確実に削除することができ、解放された領域を指すポインタが残ることはありません。
次のように定義することができます。
Bus::~Bus() { customers_.clearAndDestroy(); delete passengers_; }
多相持続性の追加方法
仮想関数 saveGuts() と restoreGuts() は、RWCollectable オブジェクトの内部状態を保存して復元する義務があります。ユーザーの RWCollectable クラスに持続性を追加するには、オブジェクトのメンバーデータすべてを書き出すように、仮想メンバー関数 saveGuts() と restoreGuts() を上書きする必要があります。347 ページの「仮想関数 saveGuts(RWFile&) と saveGuts(RWvostream&)」と、352 ページの「仮想関数 restoreGuts(RWFile&) と restoreGuts(RWvistream&)」では、これらの関数を正しく定義するために使用できる方法を説明しています。353 ページの「多重参照されるオブジェクト」では、これらの関数が多重参照されるオブジェクトをどのように処理するかを示しています。
オブジェクトをファイルに多相保存するには、そのオブジェクトを格納するために割り当てる必要があるバイト数について若干の知識が必要です。binaryStoreSize() 関数はこの値を求めます。354 ページの「仮想関数 binaryStoreSize()」では、binaryStoreSize() を使用する方法を説明します。
RWCollection には、クラスから継承するコレクションを多相保存するために使用される独自のバージョンの saveGuts() と restoreGuts() 関数があります。356 ページの「カスタムコレクションの多相持続」では、これらの関数の動作について簡単に説明します。
仮想関数 saveGuts(RWFile&) と saveGuts(RWvostream&)
仮想関数 saveGuts(RWFile&) と saveGuts(RWvostream&) は、クラス RWFile を使用してバイナリファイルに、あるいはクラス RWvostream を使用して仮想出力ストリームに RWCollectable オブジェクトの内部状態を多相保存する義務があります5。これにより、いつでも、どこでも、オブジェクトを復元することができます。次に、saveGuts() 関数を定義するためのいくつかの規則を示します。
RWvostream& operator<<(RWvostream&, const RWCString& str);
したがって、Rogue Wave クラスの多くは、簡単にストリームに挿入することができます。
RWvostream& operator<<(RWvostream&, const RWCollectable& obj);
この関数はオブジェクトに対して saveGuts() を再帰的に呼び出します。
これらの規則を念頭において、Bus の例について考えられる saveGuts() 関数の定義を見てみます。
void Bus::saveGuts(RWFile& f) const { RWCollectable::saveGuts(f); // 基底クラスを保存する f.Write(busNumber_); // 基本型を直接書き込む f << driver_ << customers_; // Rogue Wave 提供のバージョンを // 使用する f << passengers_; // NULL ポインタは自動的に検出される } void Bus::saveGuts(RWvostream& strm) const { RWCollectable::saveGuts(strm); // 基底クラスを保存する strm << busNumber_; // 基本型を直接書き込む strm << driver_ << customers_; // Rogue Wave 提供のバージョンを // 使用する strm << passengers_; // NULL ポインタは自動的に検出される } |
メンバーデータ busNumber_ は int 型であり、C++ 基本型の 1 つです。これは、RWFile::Write(int)、または RWvostream::operator<<(int) のどちらかを使用して直接保存されます。
メンバーデータ driver_ は RWCString です。これは、RWCollectable から継承されたものではありません。次のものを使用して保存されます。
RWvostream& operator<<(RWvostream&, const RWCString&);
RWvostream& operator<<(RWvostream&, const RWCollectable&);
RWvostream& operator<<(RWvostream&, const RWCollectable&);
strm << *passengers_;
RWvostream& operator<<(RWvostream&, const RWCollectable*);
仮想関数 restoreGuts(RWFile&) と restoreGuts(RWvistream&)
これらの仮想関数は、saveGuts() と似た方法で、RWCollectable の内部状態をファイルまたはストリームから復元するために使用されます。次に、Bus クラスに対するこれらの関数の定義を示します。
void Bus::restoreGuts(RWFile& f) { RWCollectable::restoreGuts(f); // 基底クラスを復元する f.Read(busNumber_); // 基本型を復元する f >> driver_ >> customers_; // Rogue Wave 提供のバージョンを // 使用する delete passengers_; // 古い RWSet を削除する f >> passengers_; // 新しいものと置換する } void Bus::restoreGuts(RWvistream& strm) { RWCollectable::restoreGuts(strm); // 基底クラスを復元する strm >> busNumber_ >> driver_ >> customers_; delete passengers_; // 古い RWSet を削除する strm >> passengers_; // 新しいものと置換する } |
ポインタ passengers_ が次のものによって復元されることに注意してください。
RWvistream& operator>>(RWvistream&, RWCollectable*&);
多重参照されるオブジェクト
乗客名は、customers_ によって指される集合と、passengers_ によって指される集合の両方に存在することができます。つまり、どちらのコレクションにも同じ文字列が含まれます。Bus を復元する場合は、ポインタの関係が維持されているか、そして、復元によって文字列の別のコピーが作成されていないかを確認する必要があります。
幸い、ポインタの関係を保証するために特殊な処理を行う必要はありません。次の呼び出しについて考えてください。
Bus aBus; RWFile aFile("busdata.dat"); aBus.addPassenger("John"); aFile << aBus;passenger_ は customer_ の部分集合であるため、addPassenger は、顧客リストと乗客リストの両方に名前を挿入します。aBus を aFile に 保存する場合、両方のリストが 1 回の呼び出しで保存されます。まず顧客リストが保存され、次に乗客リストが保存されます。多相持続性機構は John への最初の参照を保存しますが、2 番目の参照の場合は、最初のコピーへの参照を格納するだけです。復元中、両方の参照は同じオブジェクトと解釈されて、コレクションの元の構造を複写します。
仮想関数 binaryStoreSize()
binaryStoreSize() 仮想関数は、RWFile を使用してオブジェクトを格納するために必要なバイト数を計算します。この関数は次のとおりです。
virtual Rwspace binaryStoreSize() const;この関数は、オブジェクトを格納する前に領域を割り当てる必要がある クラス RWFileManager と RWBTreeOnDisk に便利です。非仮想関数 recursiveStoreSize() は、実際に格納されたバイト数を返します。recursiveStoreSize() は、binaryStoreSize() を使用してこの処理を行います。
binaryStoreSize() のバージョンの作成は通常簡単です。メンバーデータを保存するかわりにそのサイズを追加する点を除いて、saveGuts(RWFile&) によって設定されたパターンに従うだけです。実際の違いは構文に関するものだけです。つまり、挿入演算子ではなく、sizeof() と次のメンバー関数を使用します。
RWspace RWCollectable::recursiveStoreSize();
RWspace RWCollectable::nilStoreSize();
RWspace Bus::binaryStoreSize() const{ RWspace count = RWCollectable::binaryStoreSize() + customers_.recursiveStoreSize() + sizeof(busNumber_) + driver_.binaryStoreSize(); if (passengers_) count += passengers_->recursiveStoreSize(); else count += RWCollectable::nilStoreSize(); return count; } |
カスタムコレクションの多相持続
Tools.h++ がクラス RWCollection に組み込んだ saveGuts() と restoreGuts() のバージョンは、ほとんどのコレクションクラスにとって十分なものです。関数 RWCollection::saveGuts() は、コレクション内の各項目に対して、次のものを繰り返し呼び出すことによって働きます。
RWvostream& operator<<(RWvostream&, const RWCollectable&);同様に、RWCollection::restoreGuts() は、次のものを繰り返し呼び出すことによって働きます。
RWvistream& operator>>(RWvistream&, RWCollectable*&);この演算子は、正しい型の新しいオブジェクトをヒープから割り当てて、insert() を呼び出します。Rogue Wave の Smalltalk 式コレクションクラスはすべて RWCollection から継承するため、必ずこの機構を使用します。
クラス RWCollection から継承する独自のコレクションクラスを作成する場合は、独自の saveGuts() か restoreGuts() を定義する必要はほとんどありません。
ただし例外があります。たとえば、クラス RWBinaryTree は独自のバージョンの saveGuts() を持ちます。これは、デフォルトバージョンの saveGuts() が項目を順番に保存するために必要です。バイナリツリーでは、これによって、ツリーが読み戻されたときに非常にバランスの悪いツリーが生じます。つまり、リンクリストの退化したケースです。したがって、saveGuts() の RWBinaryTree バージョンでは、ツリーをレベルごとに保存します。
クラスを設計する場合は、saveGuts() と restoreGuts() のカスタムバージョンを必要とする可能性がある、これに似た特殊要件があるかどうかを判断する必要があります。
RWFactory に関する注意
RWDEFINITION_MACRO がどのように見えるかを検討します。 RWDEFINE_COLLECTABLE(className, numericID)
または、文字列 ID を使用します。
RWDEFINE_NAMED_COLLECTABLE(className, stringID)バスの例の .cpp ファイルでは、マクロは次のようになります。
RWDEFINE_COLLECTABLE(Bus, 200)および
RWDEFINE_NAMED_COLLECTABLE(Client, "a client")これらのマクロを使用すれば、RWClassID を指定するだけで、ユーザーのクラスの新しいインスタンスを作成することができます。
Bus* newBus = (Bus*)theFactory->create(200);または RWStringID を指定します。
Client* aClient = (Client*)theFactory->create("a client");ポインタ theFactory は、クラス RWFactory のある種の大域インスタンスを指す大域ポインタです。これは、実行可能プログラムにインスタンスを持つすべての RWCollectable クラスに関する情報を保持するために使用されます。RWFactory の create() メソッドは、多相持続性機構によって内部的に使用されて、実行時には型がわかっていない持続オブジェクトの新しいインスタンスを作成します。RWFactory の使用は一般的にはユーザーに透過的なため、通常、この機能をユーザーのソースコードで使用することはありません。RWFactory の詳細については、『Tools.h++ 7.0 クラスライブラリ・リファレンスマニュアル』を参照してください。
まとめ
一般に、自分でクラスを設計する場合、これらの仮想関数のすべてに関して必ずしも定義を与える必要はありません。たとえば、目的のクラスをソートされたコレクションに使用しないことがわかっている場合、compareTo() の定義は必要ありません。それでも、とにかく定義を与えておく方が得策です。それがコードの再利用を促進する最良の方法だからです。
次に、この章で例として取り上げたクラス Bus の完全なリストを示します。
BUS.H: #ifndef __BUS_H__ #define __BUS_H__ #include <rw/rwset.h> #include <rw/collstr.h> class Bus : public RWCollectable { RWDECLARE_COLLECTABLE(Bus) public: Bus(); Bus(int busno, const RWCString& driver); ~Bus(); // クラス "RWCollectable" からの継承: Rwspace binaryStoreSize() const; int compareTo(const RWCollectable*) const; RWBoolean isEqual(const RWCollectable*) const; unsigned hash() const; void restoreGuts(RWFile&); void restoreGuts(RWvistream&); void saveGuts(RWFile&) const; void saveGuts(RWvostream&) const; void addPassenger(const char* name); void addCustomer(const char* name); size_t customers() const; size_t passengers() const; RWCString driver() const {return driver_;} int number() const {return busNumber_;} private: RWSet customers_; RWSet* passengers_; int busNumber_; RWCString driver_; }; class Client : public RWCollectable { RWDECLARE_COLLECTABLE(Client) Client(); Client(const char* name); Rwspace binaryStoreSize() const; int compareTo(const RWCollectable*) const; RWBoolean isEqual(const RWCollectable*) const; unsigned hash() const; void restoreGuts(RWFile&); void restoreGuts(RWvistream&); void saveGuts(RWFile&) const; void saveGuts(RWvostream&) const; private: RWCString name_; // この例では、他のクライアントの情報を無視する }; #endif BUS.CPP: #include "bus.h" #include <rw/pstream.h> #include <rw/rwfile.h> #ifdef __GLOCK__ # include <fstream.hxx> #else # include <fstream.h> #endif RWDEFINE_COLLECTABLE(Bus, 200) Bus::Bus() : busNumber_ (0), driver_ ("Unknown"), passengers_ (rwnil) {} Bus::Bus(int busno, const RWCString& driver) : busNumber_ (busno), driver_ (driver), passengers_ (rwnil) {} Bus::~Bus() { customers_.clearAndDestroy(); delete passengers_; } RWspace Bus::binaryStoreSize() const { RWspace count = RWCollectable::binaryStoreSize() + customers_.recursiveStoreSize() + sizeof(busNumber_) + driver_.binaryStoreSize(); if (passengers_) count += passengers_->recursiveStoreSize(); return count; } int Bus::compareTo(const RWCollectable* c) const { const Bus* b = (const Bus*)c; if (busNumber_ == b->busNumber_) return 0; return busNumber_ > b->busNumber_ ? 1 : -1; } RWBoolean Bus::isEqual(const RWCollectable* c) const { const Bus* b = (const Bus*)c; return busNumber_ == b->busNumber_; } unsigned Bus::hash() const { return (unsigned)busNumber_; } size_t Bus::customers() const { return customers_.entries(); } size_t Bus::passengers() const return passengers_ ? passengers_- >entries() : 0; } void Bus::saveGuts(RWFile& f) const { RWCollectable::saveGuts(f); // 基底クラスを保存する f.Write(busNumber_); // 基本型を直接書き込む f << driver_ << customers_; // RogueWave 提供版を使用する versions f << passengers_; // NULL ポインタは自動的に検出される } void Bus::saveGuts(RWvostream& strm) const { RWCollectable::saveGuts(strm); // 基底クラスを保存する strm << busNumber_; // 基本型を直接書き込む strm << driver_ << customers_; // RogueWave 提供版を使用する strm << passengers_; // NULL ポインタは自動的に検出される } void Bus::restoreGuts(RWFile& f) { RWCollectable::restoreGuts(f); // 基底クラスを復元する f.Read(busNumber_); // 基本型を復元する f >> driver_ >> customers_; // RogueWave 提供版を使用する delete passengers_; // 古い RWSet を削除する f >> passengers_; // 新しいものと置き換える } void Bus::restoreGuts(RWvistream& strm) { RWCollectable::restoreGuts(strm); // 基底クラスを復元する strm >> busNumber_ >> driver_ >> customers_; delete passengers_; // 古い RWSet を削除する strm >> passengers_; // 新しいものと置き換える } void Bus::addPassenger(const char* name) { Client* s = new Client(name); customers_.insert( s ); if (!passengers_) passengers_ = new RWSet; passengers_->insert(s); } void Bus::addCustomer(const char* name) { customers_.insert( new Client(name) ); } /////////////// ここに Client クラスのメソッドがある ////////////// RWDEFINE_NAMED_COLLECTABLE(Client,"client") Client::Client() {} // RWCString デフォルトコンストラクタを使用する Client::Client(const char* name) : name_(name) {} RWspace Client::binaryStoreSize() const { return name_->binaryStoreSize(); } int Client::compareTo(const RWCollectable* c) const { return name_.compareTo(((Client*)c)->name_); } RWBoolean Client::isEqual(const RWCollectable* c) const { return name_ == *(Client*)c; } unsigned Client::hash() const { return name_.hash(); } void Client::restoreGuts(RWFile& f) { f >> name_; } void Client::restoreGuts(RWvistream& vis) { vis >> name_; } void Client::saveGuts(RWFile& f) const { f << name_; } void Client::saveGuts(RWvostream& vos) const { vos << name_; } main() { Bus theBus(1, "Kesey"); theBus.addPassenger("Frank"); theBus.addPassenger("Paula"); theBus.addCustomer("Dan"); theBus.addCustomer("Chris"); { // ストリーム生存期間をブロックで制御する ofstream f("bus.str"); RWpostream stream(f); stream << theBus; // theBus を ASCII ストリームに存続させる } { ifstream f("bus.str"); RWpistream stream(f); Bus* newBus; stream >> newBus; // それを ASCII ストリームから復元する cout << "Bus number " << newBus->number() << " has been restored; its driver is " << newBus->driver() << ".\n"; cout << "It has " << newBus->customers() << " customers and " << newBus->passengers() << " passengers.\n\n"; delete newBus; } return 0; } |
Bus number 1 has been restored; its driver is KeseyKesey. It has 4 customers and 2 passengers. |
2 RWDEFINITION_MACRO は、上記の 2 つのメソッドを実装する以上のことを行います。提供されたどちらのマクロも使用しないと決める前に、これらのマクロを詳しく検討して、マクロの機能をすべて理解したことを確認してください。
3 RWStringID の説明と、仮想関数を模倣する方法については、448 ページの「静的メソッドによる仮想メソッドの実装」を参照してください。このような方法でコードを作成することによって、前のバージョンの Tools.h++ からコンパイルされたオブジェクトコードとのリンクの互換性を維持します。
4 これは C++ の重大な欠点で、ユーザーは常に注意しなければなりません。特に、ユーザーが同種コレクションを持つことを予定している場合に注意が必要です。この問題については、「持続性」の「ソートされた RWCollections を使用して異種 RWCollectables を格納しない」という節を参照してください。
5 持続性機構の説明については、「持続性」の節を参照してください。