Copyright 1999 Rogue Wave Software
Copyright 1999 Sun Microsystems, Inc.
持続性 |
13 |
持続性のレベル
オブジェクトには、次の 4 つのうちいずれかの持続性レベルがあります。
用語に関する注意
Tools.h++ には、オブジェクトの保存と復元を可能にする入力クラスと出力クラスがあります。これらのクラスは次のとおりです。
この節で示す例について
この例で示す例はすべて、ディスク上のディレクトリ rw/toolexam/manual に入っています。この章で示す各例の名前は persist*.cpp です。
持続性なし
Tools.h++ クラスによっては、持続性のないものがあります。『Tools.h++ 7.0 クラスライブラリ・リファレンスマニュアル』では、このようなクラスの持続性の節にすべて「なし」と示してあります。持続性のレベルについて疑問がある場合は、特定クラスのクラスリファレンス項目を調べてください。
単純持続性
単純持続性とは、ストリームとの間におけるオブジェクトの格納と検索のことです。表 13-1 は、単純持続性を使用する Tools.h++ のクラスを示しています。
カテゴリ | 記述 |
---|---|
C++ 基本型 | int, char, float, ... |
Rogue Wave の日付クラスと時刻クラス | RWDate, RWTime |
Rogue Wave の文字列クラス | RWCString, RWWString |
その他の Rogue Wave クラス | RWBitVec |
単純持続性は簡単なため、他のオブジェクトへのポインタや仮想メンバー関数を持たないオブジェクトの保存と復元を行う場合に使いやすく、処理を迅速に行うことができます。
しかし、お互いに参照するオブジェクトを単純持続性で保存して復元すると、オブジェクト間でポインタの関係 (つまり、構造) が変更される可能性があります。これは、単純持続性では、メモリ内のオブジェクトへのポインタ参照すべてが、固有のオブジェクトを参照するものと仮定されるためです。したがって、オブジェクトが単純持続性によって保存されると、同じメモリ位置への 2 つの参照がある場合、そのメモリ位置にある内容のコピーが 2 つ保存されます。これにより、ストリーム内で余分なスペースが使用されるだけでなく、復元されたオブジェクトが、参照されるオブジェクトの 2 つの異なるコピーを指すことになります。
単純持続性の例
単純持続性を示す 2 つの例について考えてみます。最初の例では、基本データ型の正常な持続性を示して、Tools.h++ の多重定義された演算子 operator<< と operator>> について説明しています。これらの演算子は、持続オブジェクトを保存して復元します。2 番目の例では、単純持続性に関する問題の 1 つを示しています。この問題は、オブジェクト間のポインタの関係を維持できないというものです。
この例では、C++ ストリームでオブジェクトを入出力する場合と同様に、多重定義された挿入演算子の operator<< を使用してオブジェクトを保存し、多重定義された抽出演算子の operator>> を使用してそのオブジェクトを復元します。
保存ストリームと復元ストリームは、個別のブロックに入れられることに注意してください。これは、pi をオープンすると、それがファイルの最初に置かれるためです。
次にコードを示します。
#include <assert.h> #include <fstream.h> #include <rw/pstream.h> main (){ int j1 = 1; int k1 = 2; // ファイル「int.dat」に整数を保存する { // 保存用のストリームをオープンする ofstream f("int.dat"); RWpostream po(f); // 多重定義された演算子「RWpostream::operator<<(int)」を // 使用して、整数を保存する po << j1; po << k1; } // ファイル「int.dat」から整数を復元する int j2 = 0; int k2 = 0; { // 復元用の別のストリームをオープンする ifstream f("int.dat"); RWpistream pi(f); // 多重定義された抽出演算子「RWpistream::operator>>(int)」を // 使用して、整数を復元する pi >> j2; // j1 == j2 pi >> k2; // k1 == k2 } assert(j1 == j2); assert(k1 == k2); return 0; } |
前述の例では、多重定義された演算子を使用すると、このレベルの持続性をいかに簡単に実装できるかを示しています。しかし、単純持続性の使用にはいくつかの問題があります。すでに触れたように、単純持続性ではオブジェクト間のポインタの関係が維持されないという問題もそのひとつです。次の例では、この問題について考えます。
例 2:単純持続性とポインタ
次の例は、単純持続性の欠点の 1 つである、持続オブジェクト間のポインタの関係を維持できないという問題を示しています。他の Developer オブジェクトへのポインタを含むクラス Developer がある場合を想定します。
Developer { public: Developer(const char* name, const Developer* anAlias = 0L) : name_(name), alias_(anAlias) {} RWCString name_; // Developer の名前 const Developer* alias_; // 他の Developer を指す別名 }; |
さらに、Developers へのポインタの配列である Team というクラスがあるものと想定します。
class Team { public: Developer* member_[3]; };
単純持続性を使用して Developers と Teams を保存、復元する多重定義された抽出演算子と挿入演算子が記述されているものと仮定します。ここでは、説明を簡単にするために、この仮定を示すコードの例を省略します。
単純持続性によって Team を保存して復元するとき、復元する内容が保存した内容と異なる場合があります。次のコードでは、チームを作成し、それを単純持続性によって保存して復元しています。
main (){ Developer* kevin = new Developer("Kevin"); Developer* rudi = new Developer("Rudi", kevin); Team team1; team1.member_[0] = rudi; team1.member_[1] = rudi; team1.member_[2] = kevin; // 単純持続性によって保存する { RWFile f("team.dat"); f << team1; // 単純持続性によって復元する } // Restore with simple persistence: Team team2; { RWFile f("team.dat"); f >> team2; } return 0; } |
この例では、ポインタの関係を維持しない単純持続性を使用しているため、復元された team のポインタの関係は、元の team とは異なります。図 13-1 は、このプログラムを実行すると、作成された team と復元された team がどのようになるかを示しています。
保存されたコレクション (team1) |
復元されたコレクション (team2) |
同形持続性
同形持続性とは、オブジェクト間のポインタの関係が維持されるように、ストリームとの間でオブジェクトを格納して保存することです。ポインタの関係がない場合、同形持続性では、単純持続性と同じように、オブジェクトを効率的に保存して復元します。あるコレクションが同形持続される場合は、そのコレクション内のオブジェクトすべてが、同じ型を持つものと想定されます (同じ型のオブジェクトのコレクションは、同種コレクションと呼ばれます)。
同形持続性を使用すると、表 13-2 に示されたすべての Tools.h++ クラスを保存して復元することができます。さらに、258 ページの「同形持続性を使用するようにクラスを設計する」で説明する技法に従うと、同形持続性をクラスに追加することができます。
Tools.h++ のこの実装では、ポインタ上にテンプレート化された型の同形持続性がサポートされていないことに注意してください。たとえば、RWTValDlist<int*> の保存と復元はサポートされていません1。この場合は、RWTPtrDlist<int> をかわりに使用するようにしてください。
カテゴリ | 記述 |
---|---|
Rogue Wave 標準 C++ ライブラリにもとづくコレクションクラス | RWTValDeque, RWTPtrMap,... |
RWCollectable (Smalltalk 式) クラス |
RWCollectableDete, RWCollectableString,...
RWCollectable クラスも同形持続性であることに注意してください。 (289 ページの「多相持続性」を参照) |
RWCollectable から派生した RWCollection クラス | RWBinaryTree, RWBag,... |
Rogue Wave Tools.h++ 6.x のテンプレート化されたコレクション | RWTPtrDlist, RWTValDlist, RWTPtrSlist, RWTValSlist, RWTPtrOrderedVector, RWTValOrderedVector, RWTPtrSortedVector, RWTValSortedVector, RWTPtrVector, RWTValVector |
同形持続性と単純持続性の相違点
同形持続性と単純持続性の相違点を示すいくつかの図をあげます。図 13-2 では、同じオブジェクトへの複数のポインタのコレクションが、単純持続性を使用して、ストリームに保存されそこから復元されています。図 13-2 でコレクションが保存されて復元される場合、各ポインタは異なるオブジェクトを指します。これを、図 13-3 に示された同じコレクションの同形持続性と比較してください。この図では、復元されたポインタすべてが、元のコレクションのときと同様に、同じオブジェクトを指します。
単純持続性の機構では、指される各オブジェクトのコピーが作成されて、そのオブジェクトがストリームに保存されます。しかし、単純持続性機構は、どのオブジェクトを保存したかを覚えていません。ポインタに遭遇したとき、単純持続性機構は、そのポインタのオブジェクトをすでに保存したかどうかを判断できません。したがって、循環リンクリストの場合、単純持続性機構は同じオブジェクトを何度も何度も保存することになり、リストの中を永遠に循環するのです。
これに対して、図 13-5 で示すように、同形持続性を使用すると、循環リンクリストを保存することができます。同形持続性機構は、テーブルを使用して、保存したポインタを追跡します。同期持続性機構は、保存されていないオブジェクトに遭遇すると、そのオブジェクトのデータをコピーし、ポインタではなく、そのオブジェクトのデータをストリームに保存し、そのポインタを保存テーブルに記録します。同形持続性機構は、同じオブジェクトへのポインタをこの後で見つけると、そのオブジェクトのデータをコピーして保存するかわりに、保存テーブルのそのポインタへの参照を保存します。
オブジェクトへのポインタをストリームから復元する場合、同形持続性機構は、復元テーブルを使用して、逆向きの処理を行います。同形持続性機構は、復元されていないオブジェクトへのポインタに遭遇すると、ストリームからのデータを使用してそのオブジェクトを再度作成し、再度作成されたオブジェクトを指すように、復元されたポインタを変更し、そのポインタを復元テーブルに記録します。同形持続性機構は、すでに復元されたポインタへの参照に遭遇すると、復元テーブルでその参照を調べて、このテーブルで参照されるオブジェクトを指すように、復元されたポインタを更新します。
この例では、int のかわりに RWCollectableInt を使用していますが、これは、int が単純持続性を使用するためです。RWCollectableInt を使用すると、同形持続性を実装することができます。
RWTPtrDlist が保存されて復元される場合、復元されたリストのポインタの関係は、元のリストと同じ構造を持ちます。
#include <assert.h> #include <rw/tpdlist.h> // RWTPtrDlist #include <rw/collint.h> // RWCollectableInt #include <rw/rwfile.h> // RWFile main (){ RWTPtrDlist<RWCollectableInt> dlist1; RWCollectableInt *one = new RWCollectableInt(1); dlist1.insert(one); dlist1.insert(one); { RWFile f("dlist.dat"); f << dlist1; // dlist1 の同形持続性 } assert(dlist1[0] == one && dlist1[0] == dlist1[1]); // dlist1[0]、dlist[1]、「one」はすべて、同じ場所を指す。 // same place. RWTPtrDlist<RWCollectableInt> dlist2; { RWFile f("dlist.dat"); f >> dlist2; // dlist2 を f から復元する。 // dlist2 に値 1 の同じ RWCollectableInt への 2 つのポインタが // 含まれる。 // ただし、この RWCollectableInt は「one」が指す値と同じアドレス // にはない。 // dlist1 と dlist2 がメモリ内でどのようになるかについては、 // 次の図 13-6 を参照。 assert(dlist2[0] == dlist2[1] && (*dlist2[0]) == *one); // dlist2[0] と dlist2[1] は同じ場所を指し、その場所は「one」と // 同じ値を持つ。 delete dlist2[0]; delete one; // 開発者はオブジェクトを割り当てて削除しなければならない。 // テンプレート化されたコレクションメンバー関数 clearAndDestroy() は、 // 指定のポインタが 1 回だけ削除されるかどうかを検査しない。 // したがって、この場合は、共有ポインタを手作業で削除する。 return 0; } |
図 13-6 RWTPtrDlist<RWCollectableInt>
の同形保存と復元後
同形持続性を使用するようにクラスを設計する
表 13-2 は、同形持続性を実装する Tools.h++ クラスを示しています。既存のクラスにヘッダファイルしかない場合でも、そのクラスに同形持続性を追加することができます。同形持続性をクラスに追加するには、次の要件を満たしていなければなりません。
T(); // デフォルトコンストラクタ T(T& t); // コピーコンストラクタ
T& operator=(const T& t); // メンバー関数 T& operator=(T& lhs, const T& rhs); // 大域関数
同形持続性クラスを作成する場合、または既存のクラスに同形持続性を追加する場合は、次の手順に従ってください。
そのクラスのオブジェクトを再度作成するために必要な情報だけが、rwSaveGuts と rwRestoreGuts にアクセスできなければならないことに注意してください。他のデータは限定公開または非公開の状態にしておくことができます。
クラスの限定公開メンバーまたは非公開メンバーをアクセス可能にするには、いくつかの方法があります。
最初に、クラスは rwSaveGuts と rwRestoreGuts のフレンドになることができます。
class Friendly { // 次の大域関数は非公開メンバーにアクセスする friend void rwSaveGuts(RWvostream&, const Friendly&); friend void rwRestoreGuts(RWFile&, Friendly&); friend void rwSaveGuts(RWFile&, const Friendly&); friend void rwRestoreGuts(RWvistream&, Friendly&); //... };
class Accessible { public: int secret(){return secret_} void secret(const int s){secret_ = s} //... private: int secret_; };
class Unfriendly{ protected: int secret_; // ... }; class Friendlier : public Unfriendly { public: int secret(){return secret_} void secret(const int s){secret_ = s} //... };
RWDECLARE_PERSISTABLE をヘッダファイルに追加する
必要なクラスデータがすべてアクセス可能であると判断したら、宣言文をヘッダファイルに追加する必要があります。これらの文は、クラスの大域関数 operator<< と operator>> を宣言するものです。大域関数を使用すると、RWvisteam、RWvostream、RWFile との間で格納と抽出を行うことができます。
Tools.h++ には、これらの宣言を簡単に追加できるようにするためのマクロがいくつかあります。どのマクロを選択するかは、クラスがテンプレート化されているかどうかによって異なります。テンプレート化されている場合は、テンプレート化されたパラメータがいくつあるかによって異なります。
RWDECLARE_PERSISTABLE は、rw/edefs.h に入っているマクロです。これを使用するには、次の行をヘッダファイル (*.h) に追加してください。
#include <rw/edefs.h> RWDECLARE_PERSISTABLE(YourClass)
RWvostream& operator<<(RWvostream& strm, const YourClass& item); RWvistream& operator>>(RWvistream& strm, YourClass& obj); RWvistream& operator>>(RWvistream& strm, YourClass*& pObj); RWFile& operator<<(RWFile& strm, const YourClass& item); RWFile& operator>>(RWFile& strm, YourClass& obj); RWFile& operator>>(RWFile& strm, YourClass*& pObj);
RWDECLARE_PERSISTABLE_TEMPLATE は、rw/edefs.h にもあります。これを使用するには、次の行をヘッダファイル (*.h) に追加してください。
#include <rw/edefs.h> RWDECLARE_PERSISTABLE_TEMPLATE(YourClass)
template<class T> RWvostream& operator<< (RWvostream& strm, const YourClass<T>& item); template<class T> RWvistream& operator>> (RWvistream& strm, YourClass<T>& obj); template<class T> RWvistream& operator>> (RWvistream& strm, YourClass<T>*& pObj); template<class T> RWFile& operator<<(RWFile& strm, const YourClass<T>& item); template<class T> RWFile& operator>>(RWFile& strm, YourClass<T>& obj); template<class T> RWFile& operator>>(RWFile& strm, YourClass<T>*& pObj);
// YourClass<T1,T2> の場合 RWDECLARE_PERSISTABLE_TEMPLATE_2(YourClass) // YourClass<T1,T2,T3> の場合 RWDECLARE_PERSISTABLE_TEMPLATE_3(YourClass) // YourClass<T1,T2,T3,T4> の場合 RWDECLARE_PERSISTABLE_TEMPLATE_4(YourClass)
注 - テンプレート化されたクラスに型名でないテンプレートパラメータがある場合は、同形持続させることができません。
ここでも、どのマクロを使用するかは、クラスがテンプレート化されているかどうか、もしされている場合はそのクラスが必要とするパラメータの数によって決まります。
RWDEFINE_PERSISTABLE は、rw/epersist.h に入っているマクロです。これを使用するには、次の行を 1 つのソースファイル (*.cpp または *.C) だけに追加してください。
#include <rw/epersist.h> RWDEFINE_PERSISTABLE(YourClass)
RWvostream& operator<<(RWvostream& strm, const YourClass& item) RWvistream& operator>>(RWvistream& strm, YourClass& obj) RWvistream& operator>>(RWvistream& strm, YourClass*& pObj) RWFile& operator<<(RWFile& strm, const YourClass& item) RWFile& operator>>(RWFile& strm, YourClass& obj) RWFile& operator>>(RWFile& strm, YourClass*& pObj)
RWDEFINE_PERSISTABLE_TEMPLATE も rw/epersist.h に入っています。これを使用するには、次の行を 1 つのソースファイル (*.cpp または *.C) だけに追加してください。
#include <rw/epersist.h> RWDEFINE_PERSISTABLE_TEMPLATE(YourClass)
template<class T> RWvostream& operator<< (RWvostream& strm, const YourClass<T>& item) template<class T> RWvistream& operator>> (RWvistream& strm, YourClass<T>& obj) template<class T> RWvistream& operator>> (RWvistream& strm, YourClass<T>*& pObj) template<class T> RWFile& operator<<(RWFile& strm, const YourClass<T>& item) template<class T> RWFile& operator>>(RWFile& strm, YourClass<T>& obj) template<class T> RWFile& operator>>(RWFile& strm, YourClass<T>*& pObj)
// YourClass<T1,T2> の場合 RWDEFINE_PERSISTABLE_TEMPLATE_2(YourClass) // YourClass<T1,T2,T3> の場合 RWDEFINE_PERSISTABLE_TEMPLATE_3(YourClass) // YourClass<T1,T2,T3,T4> の場合 RWDEFINE_PERSISTABLE_TEMPLATE_4(YourClass)
注 - テンプレート化されたクラスに型名でないテンプレートパラメータがある場合は、同形持続させることができません。
RWvostream& operator<<(RWvostream& s, const YourClass& t); RWvistream& operator>>(RWvistream& s, YourClass& t); RWvistream& operator>>(RWvistream& s, YourClass*& pT); RWFile& operator<<(RWFile& s, const YourClass& t); RWFile& operator>>(RWFile& s, YourClass& t); RWFile& operator>>(RWFile& s, YourClass*& pT);
注 - 272 ページの「関数 rwSaveGuts と rwRestoreGuts を作成する」には、大域関数 rwSaveGuts と rwRestoreGuts の作成方法に関する指針が示してあります。
void rwSaveGuts(RWFile& f, const YourClass& t){/*...*/} void rwSaveGuts(RWvostream& s, const YourClass& t) {/*...*/} void rwRestoreGuts(RWFile& f, YourClass& t) {/*...*/} void rwRestoreGuts(RWvistream& s, YourClass& t) {/*...*/}
template<class T> void rwSaveGuts(RWFile& f, const YourClass<T>& t){/*...*/} template<class T> void wSaveGuts(RWvostream& s, const YourClass<T>& t) {/*...*/} template<class T> void rwRestoreGuts(RWFile& f, YourClass<T>& t) {/*...*/} template<class T>void rwRestoreGuts(RWvistream& s, YourClass<T>& t) {/*...*/}
関数 rwRestoreGuts は、持続性に必要な各クラスメンバーの状態を、RWvistream か RWFile から復元します。クラスのメンバーが持続可能な型である場合と、クラスのメンバーが rwRestoreGuts にアクセス可能な場合は、operator>> を使用してクラスメンバーを復元することができます。
関数 rwSaveGuts と rwRestoreGuts を作成する
次の 2 つの節では、大域関数 rwSaveGuts と rwRestoreGuts を作成するための指針について説明します。これらの指針を示すために、次のクラスを使用します。
class Gut { public: int fundamentalType_; RWCString aRogueWaveObject_; RWTValDlist anotherRogueWaveObject_; RWCollectableString anRWCollectable_; RWCollectableString* pointerToAnRWCollectable_; Gut* pointerToAnObject_; };
これにより、オブジェクトをいつでも復元することができます。
rwSaveGuts(RWFile& f, const YourClass& t) rwSaveGuts (RWvostream& s, const YourClass& t)
関数の作成方法は、メンバーデータの型によって異なります。
RWvostream& operator<<(RWvostream&, const RWCollectable*);
void rwSaveGuts(RWvostream& stream, const Gut& gut) { // 挿入演算子を使用して、基本オブジェクト、Rogue Wave オブジェクト、 // RWCollectable から派生されたオブジェクトへのポインタを保存する。 stream << gut.fundamentalType_ << gut.aRogueWaveObject_ << gut.anotherRogueWaveObject_ << gut.pointerToAnRWCollectable_; // 非 RWColletable オブジェクトへのポインタの複雑な保存 if (gut.pointerToAnObject_ == 0) // NULL ポインタか ? stream << false; // NULL ポインタであるため、保存しない。 else { stream << true; // 有効なポインタであるため、 stream << *(gut.pointerToAnObject_); // 保存する。 } } void rwSaveGuts(RWFile& stream, const Gut& gut) { // この関数の本体は、rwSaveGuts(RWvostream& stream, const Gut& gut) // と等しい。 } |
rwRestoreGuts(RWFile& f, YourClass& t) rwRestoreGuts(RWvostream& s, YourClass& t)
関数の作成方法は、メンバーデータの型によって異なります。
メンバーが、互換性のある rwSaveGuts によって保存されたと想定した場合、ポインタを復元するとき、rwRestoreGuts はまずブール値を復元します。ブール値が true であれば、rwRestoreGuts は有効なポインタを復元します。ブール値が false であれば、rwRestoreGuts はポインタを空に設定します。
RWvostream& operator>>(RWvostream&, const RWCollectable*&);
これらの指針を使用すると、次のように、例のクラス Gut の rwRestoreGuts を作成することができます。
抽出演算子を使用して、基本オブジェクト、Rogue Wave オブジェクト、RWCollectable 派生オブジェクトを復元する。
void rwRestoreGuts(RWvistream& stream, const Gut& gut) { // 非 RWColletable オブジェクトへのポインタの複雑な復元 stream >> gut.fundamentalType_ >> gut.aRogueWaveObject_ >> gut.anotherRogueWaveObject_ >> gut.pointerToAnRWCollectable_; // The tricky restoring of a pointer // to a non-RWCollectable object. bool isValid; stream >> isValid; // NULL ポインタか ? if (isValid) // 有効なポインタであるため、 stream >> gut.pointerToAnObject_; // 復元する。 else // NULL ポインタであるため、 gut.pointerToAnObject_ = rwnil; // 復元しない。 } void rwRestoreGuts(RWFile& stream, Gut& gut) { // この関数の本体は、rwRestoreGuts(RWvostream& stream, // Gut& gut) と等しい。 } |
次の例では、244 ページの「例 2:単純持続性とポインタ」で設定したコレクションに同形持続性を実装しています。Team には 3 つの Developers が含まれます。図 13-7 は、元の Team コレクションと、同形持続性によって保存して復元した後の Team コレクションの構造を示しています。
保存されるコレクション (team1) |
復元されたコレクション (team2) |
このコードは、新しい Developer と既存の Developer を区別することができます。これは、RWDEFINE_PERSISTABLE(Developer) によって生成された挿入演算子が、以前に格納された Developer を追跡するためです。この挿入演算子 operator<< は、operator<< によって Developer がまだストリームに格納されていない場合だけに、rwSaveGuts を呼び出します。
Developer オブジェクトが復元されると、抽出演算子 operator>> が、Developer に対して呼び出されます。挿入演算子と同様、抽出演算子は RWDEFINE_PERSISTABLE(Developer) によって生成されます。Developer オブジェクトがすでに復元されている場合、抽出演算子は、既存の Developer を指すように、Developer::alias_ ポインタを調整します。Developer がまだ復元されていない場合は、Developer に対して rwRestoreGuts が呼び出されます。
Developer::name_ を復元すると、Developer の rwRestoreGuts はブール値を復元して、Developer::alias_ がメモリ内の Developer を指すかどうかを判断します。ブール値が true の場合、alias_ は Developer を指すため、rwRestoreGuts はその Developer オブジェクトを復元します。さらに、rwRestoreGuts は、復元された Developer を指すように alias_ を更新します。
前述の Developer.alias_ の 同形持続性の格納処理と検索処理は、Team の Developer ポインタに適用することもできます。
次にコードを示します。
#include <iostream.h> // ユーザー出力用 #include <assert.h> #include <rw/cstring.h> #include <rw/rwfile.h> #include <rw/epersist.h> //---------------------- 宣言 ------------------------- //------------------- Developer ----------------------- class Developer { public: Developer (const char* name = "", Developer* anAlias = rwnil) : name_(name), alias_(anAlias) {} RWCString name_; Developer* alias_; }; #include <rw/edefs.h> RWDECLARE_PERSISTABLE(Developer) //--------------------- Team -------------------------- class Team { public: Developer* member_[3]; }; RWDECLARE_PERSISTABLE(Team); //---------- rwSaveGuts と rwRestoreGuts ------------- //------------------- Developer ----------------------- RWDEFINE_PERSISTABLE(Developer) // このマクロは、次の挿入演算子と抽出演算子を生成する。 // RWvostream& operator<< // (RWvostream& strm, const Developer& item) // RWvistream& operator>>(RWvistream& strm, Developer& obj) // RWvistream& operator>>(RWvistream& strm, Developer*& pObj) // RWFile& operator<<(RWFile& strm, const Developer& item) // RWFile& operator>>(RWFile& strm, Developer& obj) // RWFile& operator>>(RWFile& strm, Developer*& pObj) void rwSaveGuts(RWFile& file, const Developer& developer){ // Called by: // RWFile& operator<<(RWFile& strm, const Developer& item) file << developer.name_; // 名前を保存する。 // alias_ がメモリ内の Developer を指すかどうかを確認する。 // 指していない場合は、ブール値の false を格納して、 // alias_ が空ポインタであることを示す。 // alias_ が Developer を指す場合は、rwSaveGuts はブール値の true を // 格納して、alias_ が指す Developer の値を格納する。 if (developer.alias_ == rwnil) file << false; // alias なし else { file << true; file << *(developer.alias_); // alias を保存する } } void rwSaveGuts(RWvostream& stream, const Developer& developer) { // 次のコードによって呼び出される。 // RWvostream& operator<< // (RWvostream& strm, const Developer& item) stream << developer.name_; // Save name. // alias_ がメモリ内の Developer を指すかどうかを確認する。 if (developer.alias_ == rwnil) stream << false; // alias なし else { stream << true; stream << *(developer.alias_); // alias を保存する } } void rwRestoreGuts(RWFile& file, Developer& developer) { // 次のコードによって呼び出される。 // RWFile& operator>>(RWFile& strm, Developer& obj) file >> developer.name_; // 名前を復元する。 // developer.alias_ が Developer を指すかどうか ? RWBoolean alias; file >> alias; // alias_ が Developer を指す場合、rwRestoreGuts は // Developer オブジェクトを復元してから、新しい Developer を指すように、 // alias_ を更新する。 file >> developer.alias_; // 次のコードを呼び出す。 // RWFile& operator>>(RWFile& strm, Developer*& pObj) } void rwRestoreGuts(RWvistream& stream, Developer& developer) { // 次のコードによって呼び出される。 // RWvistream& operator>>(RWvistream& strm, Developer& obj) stream >> developer.name_; // 名前を復元する。 // developer.alias_ が Developer を指すかどうか ? RWBoolean alias; stream >> alias; if (alias) // 指す。 stream >> developer.alias_; // alias を復元して、ポインタを更新する。 // 次のコードを呼び出す // RWvistream& operator>> // (RWvistream& strm, Developer*& pObj) } // ユーザー出力だけ ostream& operator<<(ostream& stream, const Developer& d) { stream << d.name_ << " at memory address: " << (void*)&d; if (d.alias_) stream << " has an alias at memory address: " << (void*)d.alias_ << " "; else stream << " has no alias."; return stream; } //--------------------- Team ------------------------------- RWDEFINE_PERSISTABLE(Team); // このマクロは、次の挿入演算子と抽出演算子を生成する。 // RWvostream& operator<< // (RWvostream& strm, const Team& item) // RWvistream& operator>>(RWvistream& strm, Team& obj) // RWvistream& operator>>(RWvistream& strm, Team*& pObj) // RWFile& operator<<(RWFile& strm, const Team& item) // RWFile& operator>>(RWFile& strm, Team& obj) // RWFile& operator>>(RWFile& strm, Team*& pObj) void rwSaveGuts(RWFile& file, const Team& team){ // RWFile& operator<<(RWFile& strm, const Team& item) によって // 呼び出される。 for (int i = 0; i < 3; i++) file << *(team.member_[i]); // Developer の値を保存する。 // 次のコードを呼び出す。 // RWFile& operator<< // (RWFile& strm, const Developer& item) } void rwSaveGuts(RWvostream& stream, const Team& team) { // 次のコードによって呼び出される。 // RWvostream& operator<<(RWvostream& strm, const Team& item) for (int i = 0; i < 3; i++) stream << *(team.member_[i]); // Developer の値を保存する。 // 次のコードを呼び出す。 // RWvostream& operator<< // (RWvostream& strm, const Developer& item) } void rwRestoreGuts(RWFile& file, Team& team) { // RWFile& operator>>(RWFile& strm, Team& obj) によって呼び出される。 for (int i = 0; i < 3; i++) file >> team.member_[i]; // Developer を復元して、ポインタを更新する。 // 次のコードを呼び出す。 // RWFile& operator>>(RWFile& strm, Developer*& pObj) } void rwRestoreGuts(RWvistream& stream, Team& team) { // 次のコードによって呼び出される // RWvistream& operator>>(RWvistream& strm, Team& obj) for (int i = 0; i < 3; i++) stream >> team.member_[i]; // Developer を復元して、ポインタを更新する。 // 次のコードを呼び出す。 // RWvistream& operator>> // (RWvistream& strm, Developer*& pObj) } // 表示の目的だけ ostream& operator<<(ostream& stream, const Team& t) { for (int i = 0; i < 3; i++) stream << "[" << i << "]:" << *(t.member_[i]) << endl; return stream; } //-------------------- main -------------------------- main (){ Developer* kevin = new Developer("Kevin"); Developer* rudi = new Developer("Rudi", kevin); Team team1; team1.member_[0] = rudi; team1.member_[1] = rudi; team1.member_[2] = kevin; cout << "team1 (before save):" << endl << team1 << endl << endl; // 表示する。 { RWFile f("team.dat"); f << team1; // team の同形持続性 } Team team2; { RWFile f("team.dat"); f >> team2; } cout << "team2 (after restore):" << endl << team2 << endl << endl; // 表示する。 delete kevin; delete rudi; return 0; } |
team1 (before save): [0]:Rudi at memory address: 0x10002be0 has an alias at memory address: 0x10002bd0 [1]:Rudi at memory address: 0x10002be0 has an alias at memory address: 0x10002bd0 [2]:Kevin at memory address: 0x10002bd0 has no alias. team2 (after restore): [0]:Rudi at memory address: 0x10002c00 has an alias at memory address: 0x10002c10 [1]:Rudi at memory address: 0x10002c00 has an alias at memory address: 0x10002c10 [2]:Kevin at memory address: 0x10002c10 has no alias. |
Tools.h++ は、RWCollectable から派生したクラスを使用して、多相持続性を実現します。これらのクラスから作成されたオブジェクトは、RWColletable から派生した異なる型のいずれかである場合があります。このようなオブジェクトのグループは異種コレクションと呼ばれ、オブジェクトの型が異なる場合があります。
表 13-3 は、多相持続性を使用するクラスを示しています。
カテゴリ | 記述 |
---|---|
RWColletable (Smalltalk 式) クラス | RWCollectableDate, RWCollectableString... |
RWCollection クラス (RWCollectable から派生) | RWBinaryTree, RWBag... |
演算子
RWCollectable から継承する多相オブジェクトの格納と検索は、Tools.h++ クラスライブラリの強力で柔軟な機能です。他の持続性機構と同様、多相持続性は、多重定義された抽出演算子と挿入演算子 (operator<< と operator>> 多相持続) を使用します。これらの演算子を多相持続性で使用すると、同形保存され復元されたオブジェクトだけでなく、未知の型のオブジェクトも復元することができます。
Rwvostream& operator<<(RWvostream&, const RWCollectable&); RWFile& operator<<(RWFile&, const RWCollectable&);
RWCollectable 派生オブジェクトはそれぞれ、そのオブジェクトのクラスを固有に識別するクラス ID によって同形保存されます。
Rwvostream& operator<<(RWvostream&, const RWCollectable*); RWFile& operator<<(RWFile&, const RWCollectable*);
オブジェクトへの各ポインタは、そのオブジェクトのクラスを固有に識別するクラス ID によって同形保存されます。NULL ポインタも保存することができます。
Rwvistream& operator>>(RWvistream&, RWCollectable&); RWFile& operator>>(RWFile&, RWCollectable&);
RWCollectable 派生オブジェクトはそれぞれ同形復元されます。持続性機構は、オブジェクトとともに格納されたクラス ID を調べて、実行時にオブジェクトの型を判別します。
Rwvistream& operator>>(RWvistream&, RWCollectable*&); RWFile& operator>>(RWFile&, RWCollectable*&);
RWCollectable から派生した各オブジェクトは同形復元され、ポインタ参照は、復元されたオブジェクトを指すように更新されます。持続性機構は、オブジェクトとともに格納されたクラス ID を調べて、実行時にオブジェクトの型を判別します。復元されたオブジェクトは、ヒープから割り当てられるため、これらのオブジェクトの処理を終えたら、ユーザーがそれらを削除しなければなりません。
多相持続性の例
この多相持続性の例には、2 つの異なるプログラムが含まれます。最初の例では、コレクションの内容を標準出力 (stdout) に多相保存します。2 番目の例では、保存されたコレクションの内容を標準入力 (stdin) から多相復元します。持続性を使用して、2 つの異なるプロセス間でオブジェクトを共有できることを示すために、例を 2 つに分けました。
最初の例をコンパイルして実行する場合、出力はファイルに保存されるため、オブジェクトになります。ただし、最初の例の出力を 2 番目の例にパイプすることができます。
firstExample | secondExample
例 1 では、同じオブジェクトの 2 つのコピーと、他の 2 つのオブジェクトが含まれるコレクションを作成して保存していることに注意してください。これらの 4 つのオブジェクトの型は 3 つあります。例 1 がコレクションを保存し、例 2 がそのコレクションを復元するとき、次のことがわかります。
#include <rw/ordcltn.h> #include <rw/collstr.h> #include <rw/collint.h> #include <rw/colldate.h> #include <rw/pstream.h> main(){ // 空のコレクションを作成する RWOrdered collection; // オブジェクトをそのコレクションに挿入する RWCollectableString* george; george = new RWCollectableString("George"); collection.insert(george); // 文字列を 1 回追加する collection.insert(george); // 文字列を 2 回追加する collection.insert(new RWCollectableInt(100)); collection.insert(new RWCollectableDate(3, "May", 1959)); // 可搬なストリームを使用して cout に「格納」する RWpostream ostr(cout); ostr << collection; // 上の文は挿入演算子を呼び出す // Rwvistream& // operator<<(RWvistream&, const RWCollectable&); // ここで、コレクション内のすべてのメンバーを削除する // clearAndDestroy() は、各オブジェクトを 1 回だけ削除するように // 作成されているため、同じオブジェクトを何度も削除する必要はない collection.clearAndDestroy(); return 0; } |
collection には 3 つの型のオブジェクトが格納されていることに注意してください。つまり、RWCollectableDate、RWCollectableInt、2 つの RWCollectableString です。同じ RWCollectableString の george が、collection に 2 回挿入されます。
例 2:多相復元
2 番目の例は、最初の例で多相保存されたコレクションを、多重定義された抽出演算子によって、どのように読み取り復元するかを示しています。
Rwvistream& operator>>(RWvistream&, RWCollectable&);
istr >> collection2;
持続性は次のようにして起こります。入力ストリーム istr から collection2 に復元された、RWCollectable から派生されたオブジェクトへの各ポインタのそれぞれに対して、抽出演算子 operator>> は、各種の多重定義された抽出演算子と持続性関数を呼び出します。各 RWCollectable 派生オブジェクトのポインタのそれぞれに対して、collection2 の抽出演算子は、次のことを行います。
次に、例を示します。
#define RW_STD_TYPEDEFS #include <rw/ordcltn.h> #include <rw/collstr.h> #include <rw/collint.h> #include <rw/colldate.h> #include <rw/pstream.h> main(){ RWpistream istr(cin); RWOrdered collection2; // このプログラムは復元対象を正確に知る必要はないが、RWFactory // によって使用されるために必要なコードをリンクするために、リンカー // はその可能性だけは知っておく必要がある。RWFactory は クラス ID // にもとづいて RWCollectable オブジェクトを作成する。 RWCollectableInt exemplarInt; RWCollectableDate exemplarDate; // コレクションを読み戻す istr >> collection2; // 注:上の文は、コレクションを復元するコードである。 // この例の後半は、コレクションの内容を示す。 // 「George」という値を持つ文字列を検索するために、 // 「George」という値を持つ文字列を一時的に作成する。 RWCollectableString temp("George"); // 「George」を検索する // 値「George」を持つ文字列の出現がないか、collection2 が // 検索される。 // ポインタ「g」は、このような文字列を指す。 RWCollectableString* g; g = (RWCollectableString*)collection2.find(&temp); // 「g」は値が「George」の文字列を指す。 // コレクション内の g の出現回数は ? size_t georgeCount = 0; size_t stringCount = 0; size_t integerCount = 0; size_t dateCount = 0; size_t unknownCount = 0; // 反復子を作成する RWOrderedIterator sci(collection2); RWCollectable* item; // コレクションを項目単位で反復して、各項目へのポインタを返す while ( item = sci() ) { // このポインタが g に等しいかどうかをテストする // つまり、等値性ではなく、同一性をテストする if (item->isA() == __RWCOLLECTABLESTRING && item==g) georgeCount++; // 文字列、日付、整数をカウントする switch (item->isA()) { case __RWCOLLECTABLESTRING: stringCount++; break; case __RWCOLLECTABLEINT: integerCount++; break; case __RWCOLLECTABLEDATE: dateCount++; break; default: unknownCount++; break; } } // 結果を表示する cout << "There are:\n\t" << stringCount << " RWCollectableString(s)\n\t" << integerCount << " RWCollectableInt(s)\n\t" << dateCount << " RWCollectableDate(s)\n\t" << unknownCount << " other RWCollectable(s)\n\n" << "There are " << georgeCount << " pointers to the same object \"George\"" << endl; // 作成されたオブジェクトすべてを削除して戻る collection2.clearAndDestroy(); return 0; } |
There are: 2 RWCollectableString(s) 1 RWCollectableInt(s) 1 RWCollectableDate(s) 0 other RWCollectable(s) There are 2 pointers to the same object "George" |
保存されるコレクション (collection1) |
復元されたコレクション (collection2) |
istr >> collection2;
RWvistream& operator>>(RWvistream& str, RWCollectable& obj);
RWvistream& operator>>(RWvistream&, RWCollectable*&);
多相持続性機構に関するこのような詳細は、第 14 章「RWCollectable クラスの設計」で説明するように、独自の多相持続可能クラスを設計するときに特に重要です。また、このようなクラスを処理する場合は、Smalltalk 式コレクションクラスを復元するときに、復元されるオブジェクトの型がまったくわからないことに注意してください。したがって、復元処理は、これらのオブジェクトを常にヒープから割り当てなければなりません。つまり、ユーザーが復元された内容を削除しなければならないということです。この例は、両方の多相持続性の例の終わりで起こっています。
使用する持続演算子を選択する
2 番目の例では、持続演算子は、次のように、コレクションを RWCollectable への参照に復元していました。
Rwvistream& operator>>(RWvistream&, RWCollectable&);
Rwvistream& operator>>(RWvistream&, RWCollectable*&);
RWpistream istr(cin); RWOrdered collection2; istr >> collection2; ... collection2.clearAndDestroy();
RWpistream istr(cin); RWOrdered* pCollection2; istr >> pCollection2; ... collection->clearAndDestroy(); delete pCollection2;
Rwvistream& operator>>(RWvistream&, RWCollectable&);
その他の注意事項
持続性は便利なものですが、注意を要する場合もあります。次に、オブジェクトに対して持続性機構を使用するときに注意すべき 2、3 の点を示します。 オブジェクトを保存する場合は、必ずそのオブジェクトを値によって保存してから、ポインタによって保存する
オブジェクトの同形持続性の場合でも多相持続性の場合でも、あるオブジェクトを必ず値によってストリーム出力してから、ポインタによってストリーム出力しなければなりません。値とその値へのポインタを含むクラスを設計する場合、そのクラスのメンバー関数 saveGuts と restoreGuts は、必ず値を保存または復元してから、ポインタを保存または復元しなければなりません。
次に、同形持続性を使用するクラスを作成して、そのクラスのオブジェクトをインスタンス化して、これらのオブジェクトを同形持続しようとする例を示します。この例は正常に実行されません。理由については、説明を参照してください。
class Elmer { public: /* ... */ Elroy elroy_; Elroy* elroyPtr_; // elroyPtr_ は elroy_ を指す }; RWDEFINE_PERSISTABLE(Elmer) void rwSaveGuts(RWFile& file, const Elmer& elmer) { // elroyPtr_ の値を作成してそれを保存する file << *elroyPtr_; // elroyPtr_ == &elroy_ の場合は、elroyPtr_ 用に作成された // 値を指す、elroy_ への参照を格納する file << elroy_; } void rwRestoreGuts(RWFile& file, Elmer& elmer){ // メモリーに elroyPtr_ の値を作成して、メモリー内のその値を // 指すように、elroyPtr_ を変更する file >> elroyPtr_; // 値への参照を割り当てる。 // elroyPtr_==&elroy の場合は、elroy_ の値はすでに作成されている // はずだが、elroyPtr_ != &elroy であるので、RWTOOL_REF 例外が // 送出される file >> elroy_; } /* ... */ RWFile file("elmer.dat"); Elmer elmer; Elmer elmer2; elmer.elroyPtr_ = &(elmer.elroy_); /* ... */ file << elmer; // 障害が始まる /* ... */ file >> elmer2; // 障害が起こる。RWTOOL_REF 例外。 /* ... */ |
このコードでは、次の文が elmer を file に同形保存します。
file << elmer;
operator<<(RWFile&, const Elmer&)
関数 rwSaveGuts(RWFile&, const Elmer&) は、elroyPtr_ をまず保存してから、elroy_ を保存します。rwSaveGuts は、値 *elroyPtr_ を保存するとき、挿入演算子 operator<<(RWFile&, const Elroy&) を呼び出します。
挿入演算子 operator<<(RWFile&, const Elroy&) は、elmer.elroyPtr_ がまだ保存されていないことを確認して、値 *(elmer.elroyPtr_) を file に保存して、同形保存テーブルにこの値が格納されたことを示す印を作成します。さらに、operator<<(RWFile&, const Elroy&) は rwSaveGuts(RWFile&, const Elmer&) に戻ります。
rwSaveGuts(RWFile&, const Elmer&) に戻ったら、elmer.elroy_ を保存します。この例では、elmer.elroyPtr_ のアドレスは elmer.elroy_ と同じです。もう一度、挿入演算子 operator<<(RWFile&, const Elroy&) が呼び出されますが、今回、挿入演算子は同形保存テーブルで *(elmer.elroyPtr_) がすでに格納されていることに気づくため、値ではなく、*(elmer.elroyPtr_) への参照が file に格納されます。
すべて良好に見えますが、障害が始まっています。障害は次の文とともに起こります。
file >> elmer2;
関数 rwRestoreGuts(RWFile&, Elmer&) は、elroyPtr_ をまず復元してから、elroy_ を復元します。rwRestoreGuts は値 *elroyPtr_ を復元するとき、抽出演算子 operator>>(RWFile&, Elroy*&) を呼び出します。
抽出演算子 operator>>(RWFile&, Elroy*&) は、elmer2.elroyPtr_ がまだ file から抽出されていないことを確認します。したがって、抽出演算子は値 * (elmer2.elroyPtr_) を file から抽出し、メモリーを割り当ててこの値を作成し、この値を指すように、elmer2.elroyPtr_ を更新します。さらに抽出演算子は、同形復元テーブルに、この値が作成されたことを示す印を作成します。印を作成すると、operator<<(RWFile&, Elroy&) は rwRestoreGuts(RWFile&, Elmer&) に戻ります。
rwRestoreGuts(RWFile&, Elmer&) に戻ったら、elmer2.elroy_ を復元します。elmer.elroyPtr_ のアドレスは elmer.elroy_ と同じあることを思い出してください。rwRestoreGuts 関数は、抽出演算子 operator>>(RWFile&, Elroy&) を呼び出して、同形保存テーブルで *(elmer2.elroyPtr_) がすでに file から抽出されていることに気づきます。値はすでに復元テーブルに格納されているため、抽出演算子は、値ではなく、*(elmer.elroyPtr_) への参照を file から抽出します。しかし、抽出演算子は、elmer2.elroyPtr_ が復元テーブルに挿入した値のアドレスが、elmer2.elroy_ のアドレスと異なることに気づきます。したがって、operator>>(RWFile&, Elroy&) は RWTOOL_REF 例外を送出し、復元は中止されます。
このような場合の問題の解決方法は簡単です。Elmer のメンバーの保存と復元の順序を逆にするだけです。次の問題を示します。
// 間違い void rwSaveGuts(RWFile& file, const Elmer& elmer) { file << *elroyPtr_; file << elroy_; } void rwRestoreGuts(RWFile& file, Elmer& elmer){ file >> elroyPtr_; file >> elroy_; }
// 正しい void rwSaveGuts(RWFile& file, const Elmer& elmer) { file << elroy_; file << *elroyPtr_; } void rwRestoreGuts(RWFile& file, Elmer& elmer){ file >> elroy_; file >> elroyPtr_; }
要約:同じクラス内に、ある値とその値を指すポインタの両方が存在する可能性があるため、rwSaveGuts と rwRestoreGuts の各メンバー関数は、常に値を処理してからポインタを処理するように定義してください。
同じアドレスを持つ異なるオブジェクトを保存しない
同じアドレスを持つ可能性がある異なるオブジェクトを同形保存しないように注意してください。同形持続性と多相持続性で使用される内部テーブルは、オブジェクトのアドレスを使用して、オブジェクトがすでに保存されているかどうかを判別します。
次の例は、Godzilla と Mothra を同形持続可能にするための処理がすべて実行されたものと想定しています。
class Mothra {/* ... */}; RWDEFINE_PERSISTABLE(Mothra) struct Godzilla { Mothra mothra_; int wins_; }; RWDEFINE_PERSISTABLE(Godzilla) /*... */ Godzilla godzilla; /* ... */ stream << godzilla; /* ... */ stream >> godzilla; // 復元に異常がある可能性がある
ここで問題になるのは、コンパイラによっては、godzilla と godzilla.mothra_ が同じアドレスを持つという点です。godzilla の復元時に、godzilla.mothra_ は値として保存され、godzilla は godzilla.mothra_ への参照として保存されます。godzilla と godzilla.mothra_ のアドレスが同じである場合、抽出演算子は godzilla を godzilla.mothra_ の内容によって初期化しようとするため、godzilla の復元は失敗します。
この問題を解決する方法は 2 つあります。最初の方法では、int などの単純なデータメンバーが、同形持続性のあるデータメンバーの前にくるようにクラスを作成します。この方法を使用すると、クラス Godzilla は次のようになります。
struct Godzilla { int wins_; Mothra mothra_; // mothra_ は、この時点で異なるアドレスを持つ };
クラスとそのメンバーが同じアドレスを持つという問題を解決する 2 番目の方法では、同形持続可能なメンバーを、値ではなく、ポインタとして挿入します。Godzilla は次のようになります。
struct Godzilla { Mothra* mothraPtr_;// mothraPtr_ は異なるアドレスを指す int wins_; };
ソートされた RWCollections を使用して異種 RWCollectables を格納しない
複数の異なる型の RWCollectable が 1 つの RWCollection に格納されている場合は、ソートされた RWCollection を使用することができません。たとえば、RWCollectableString と RWCollectableDates を同じ RWCollection に格納する場合、それらを RWBTree などのソートされた RWCollection に格納することができないということです。ソートされた RWCollections には、RWBinaryTree、RWBtree、RWBTreeDistionary、RWSortedVector があります。
この制約は、ソートされた RWCollections の比較関数が、比較されるオブジェクトが同じ型を持つことを期待するために発生します。
復元されるすべての RWCollectables を定義する
プログラムで、復元する可能性のある RWCollectable オブジェクトすべての変数を宣言していることを確認してください。この実際の例については、297 ページの「例 2:多相復元」を参照してください。
これらの宣言は、RWCollectable をあるコレクションに保存したときに、保存した RWCollectable を使用せず、異なるプログラム内でそのコレクションを復元することによって多相持続性を利用しようとするときに特に重要です。復元処理中に適切な変数を宣言しないと、RWFactory は、存在することがわかっているいくつかの RWCollectable クラス ID に RS_NOCREATE 例外を送出します。RWFactory は、多相復元可能な RWCollectables すべての変数を宣言するときは、RW_NOCREATE 例外を送出しません。
問題は、欠落している RWCollectable が各自のコードで特に言及されているときに、その RWCollectable を作成するために RWFactory が必要とするコードだけを、コンパイラのリンカーがリンクするために起こります。欠落した RWCollectables を宣言すると、RWFactory が必要とする適切なコードにリンクするために必要な情報を、リンカーに与えることができます。
2 テンプレートクラスの場合、一部のコンパイラでは、ソースファイルが、RWDECLARE_PERSISTABLE が使用されたヘッダファイルと同じ基底名を持たなければならない場合があります。
3 実際には、Smalltalk 式コレクションクラスは非常に似ているため、どれも RWCollection から継承された 同じバージョンの RestoreGuts() を共有しています。