Tools.h++ 7.0 ユーザーズガイド: 13 - 持続性

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

# 持続性 | # 13 |

## 持続性とは

持続性とは、ファイルまたはストリームにオブジェクトを保存して、そのファイルまたはオブジェクトからそのオブジェクトを復元する機能のことです。持続性は、プロセス間のオブジェクトの交換を容易にするため、非常に重要なオブジェクトの機能のひとつです。持続性を利用してストリームを処理すると、あるプログラムから別のプログラムに、またはあるユーザーから別のユーザーにオブジェクトを送信することができます。また、ディスク上のファイルへ持続オブジェクトを保存すると、いつでも、どこでも、そのオブジェクトをディスクから復元することができます。

### 持続性のレベル

オブジェクトには、次の 4 つのうちいずれかの持続性レベルがあります。

- 持続性なし。オブジェクトの格納と検索を行う機構がありません。

- 単純持続性。ストリーム間、あるいはファイル間で、各オブジェクトの格納と検索を可能にする持続性のレベルです。単純持続性は、持続オブジェクト間でポインタの関係を維持しません。

- 同形持続性。持続オブジェクト間でポインタの関係を維持する持続性のレベルです。

- 多相持続性。最上位レベルの持続性です。多相持続性は、持続オブジェクト間でポインタの関係を維持し、あるオブジェクトの型を知らなくても、復元処理でそのオブジェクトを復元できるようにします。

『Tools.h++ 7.0 クラスライブラリ・リファレンスマニュアル』には、各クラスの持続性レベルが示されます。この節では、独自の持続クラスを設計するための説明、例、手順を通じて、持続性の各レベルに関する情報を示します。

### 用語に関する注意

Tools.h++ には、オブジェクトの保存と復元を可能にする入力クラスと出力クラスがあります。これらのクラスは次のとおりです。

- RWFile − RWFile を使用すると、ファイルへのオブジェクトの保存と復元が可能になります。

- RWvostream − RWvostream から派生したクラス (RWpostream、RWbostream、RWeostream など) は、オブジェクトの保存に使用されます。

- RWvistream − RWvistream から派生したクラス (RWpistream、RWbistream、RWeistream など) は、オブジェクトの復元に使用されます。

説明をわかりやすくするために、これらの入力クラスと出力クラスをすべてストリームと呼ぶことにします。RWFile に対して、RWvostream と RWvistream を使用した場合の長所と短所の説明については、第 5 章「仮想ストリーム」と第 6 章「クラス RWFile の使用法」を参照してください。

### この節で示す例について

この例で示す例はすべて、ディスク上のディレクトリ `rw/toolexam/manual` に入っています。この章で示す各例の名前は `persist*.cpp` です。

## 持続性なし

Tools.h++ クラスによっては、持続性のないものがあります。『Tools.h++ 7.0 クラスライブラリ・リファレンスマニュアル』では、このようなクラスの持続性の節にすべて「なし」と示してあります。持続性のレベルについて疑問がある場合は、特定クラスのクラスリファレンス項目を調べてください。

## 単純持続性

単純持続性とは、ストリームとの間におけるオブジェクトの格納と検索のことです。表 13-1 は、単純持続性を使用する Tools.h++ のクラスを示しています。

#### 表 13-1 単純持続性を持つクラス

カテゴリ | 記述
--- | ---
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 つを示しています。この問題は、オブジェクト間のポインタの関係を維持できないというものです。

#### 例 1：基本型のオブジェクトの単純な持続

この例では、単純持続性を使用して、2 つの整数を出力ストリーム `po` に保存します。この出力ストリームは、これらの整数をファイル `int.dat` に保存します。さらに例では、2 つの整数をストリーム `pi` から復元しています。これは、ファイル `int.dat` から整数を読み取ります。

この例では、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];
      };

`Team::member_` は、実際には `Developers` を含まず、`Developers` へのポインタだけを含むことに注意してください。

単純持続性を使用して `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-1 単純持続性

図 13-1 を見ればわかるように、相互に参照しあう複数のオブジェクトが、単純持続性によって保存されて復元されると、オブジェクト間の構造が変更されます。これは、単純持続性では、メモリ内のオブジェクトを参照するポインタすべてが固有のオブジェクトを参照するものと仮定されているためです。したがって、このようなオブジェクトが保存されると、同じメモリ位置への 2 つの参照がある場合、そのメモリ位置の内容のコピーが 2 つ保存されて、後に復元されます。

## 同形持続性

同形持続性とは、オブジェクト間のポインタの関係が維持されるように、ストリームとの間でオブジェクトを格納して保存することです。ポインタの関係がない場合、同形持続性では、単純持続性と同じように、オブジェクトを効率的に保存して復元します。あるコレクションが同形持続される場合は、そのコレクション内のオブジェクトすべてが、同じ型を持つものと想定されます (同じ型のオブジェクトのコレクションは、同種コレクションと呼ばれます)。

同形持続性を使用すると、表 13-2 に示されたすべての Tools.h++ クラスを保存して復元することができます。さらに、258 ページの「同形持続性を使用するようにクラスを設計する」で説明する技法に従うと、同形持続性をクラスに追加することができます。

Tools.h++ のこの実装では、ポインタ上にテンプレート化された型の同形持続性がサポートされていないことに注意してください。たとえば、RWTValDlist`<int*>` の保存と復元はサポートされていません1。この場合は、RWTPtrDlist`<int>` をかわりに使用するようにしてください。

#### 表 13-2 同形持続性クラス

カテゴリ | 記述
--- | ---
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-2 単純持続性を使用したコレクションの保存と復元

##### 図 13-3 同形持続性を使用したコレクションの保存と復元

図 13-4 では、単純持続性を使用して、循環リンクリストを保存して復元しようとしています。この図に示されているように、単純持続性を使用して循環リンクリストを保存しようと試みると、必ず無限ループが起こります。

単純持続性の機構では、指される各オブジェクトのコピーが作成されて、そのオブジェクトがストリームに保存されます。しかし、単純持続性機構は、どのオブジェクトを保存したかを覚えていません。ポインタに遭遇したとき、単純持続性機構は、そのポインタのオブジェクトをすでに保存したかどうかを判断できません。したがって、循環リンクリストの場合、単純持続性機構は同じオブジェクトを何度も何度も保存することになり、リストの中を永遠に循環するのです。

これに対して、図 13-5 で示すように、同形持続性を使用すると、循環リンクリストを保存することができます。同形持続性機構は、テーブルを使用して、保存したポインタを追跡します。同期持続性機構は、保存されていないオブジェクトに遭遇すると、そのオブジェクトのデータをコピーし、ポインタではなく、そのオブジェクトのデータをストリームに保存し、そのポインタを保存テーブルに記録します。同形持続性機構は、同じオブジェクトへのポインタをこの後で見つけると、そのオブジェクトのデータをコピーして保存するかわりに、保存テーブルのそのポインタへの参照を保存します。

オブジェクトへのポインタをストリームから復元する場合、同形持続性機構は、復元テーブルを使用して、逆向きの処理を行います。同形持続性機構は、復元されていないオブジェクトへのポインタに遭遇すると、ストリームからのデータを使用してそのオブジェクトを再度作成し、再度作成されたオブジェクトを指すように、復元されたポインタを変更し、そのポインタを復元テーブルに記録します。同形持続性機構は、すでに復元されたポインタへの参照に遭遇すると、復元テーブルでその参照を調べて、このテーブルで参照されるオブジェクトを指すように、復元されたポインタを更新します。

##### 図 13-4 単純持続性による循環リンクリストの保存と復元の試み

##### 図 13-5 同形持続性による循環リンクリストの保存と復元

### Tools.h++ クラスの同形持続性

次の例は、RWCollectable 整数、RWTPtrDlist<RWCollectableInt> のテンプレート化されたコレクションの同形持続性を示しています。RWTPtrDlist は、テンプレート化された、参照にもとづく、二重リンクリストで、同形持続性を使用してメモリ内の値へのポインタ参照を格納します。

この例では、`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);                  // コピーコンストラクタ

- クラス `T` に、メンバー関数または大域関数として定義された割り当て演算子がなければなりません。

        T& operator=(const T& t);           // メンバー関数
        T& operator=(T& lhs, const T& rhs); // 大域関数

- クラス T が、型名でないテンプレートパラメータを持ってはなりません。たとえば、RWTBitVec<size> では、「size」は型ではなく値を入れる部分です。現在のコンパイラは、型名でないテンプレートパラメータを持つ関数テンプレートを受け入れません。また、テンプレート化されたクラスを持続させるために使用されるとき、同形持続性を実装するために使用される大域関数 (`rwRestoreGuts` と `RWSaveGuts`) は関数テンプレートです。

- クラス T は、マクロ `RW_DECLARE_PERSISTABLE` と `RW_DEFINE_PERSISTABLE`、またはそれに相当するもの使用しなければなりません。この詳細については、263 ページの「RWDECLARE_PERSISTABLE をヘッダファイルに追加する」と 266 ページの「RWDEFINE_PERSISTABLE を 1 つのソースファイルに追加する」を参照してください。

- クラス T のインスタンスを再度作成するために必要はデータはすべて、大域で使用可能でなければなりません (アクセス関数がなければなりません)。このデータを使用可能にできない場合、同形持続性を実装することができません。この詳細については、261 ページの「必要なすべてのクラスデータを使用可能にする」を参照してください。

クラス T が標準 C++ ライブラリのコンテナか標準 C++ ライブラリにもとづくコレクションに格納される場合は、`operator<(constT&, const T&)` と `operator==(const T&, const T&)` を実装する必要があります。詳細については、112 ページの「移行の手引き：旧バージョンの Tools.h++ を使用している場合」を参照してください。

同形持続性クラスを作成する場合、または既存のクラスに同形持続性を追加する場合は、次の手順に従ってください。

1. 必要なクラスデータをすべて使用可能にします。

2. `RWDECLARE_PERSISTABLE` をヘッダファイルに追加します。

3. `RWDEFINE_PERSISTABLE` を 1 つのソースファイルに追加します。

4. 問題の可能性がないかを調べます。

5. `rwSaveGuts` と `rwRestoreGuts` を定義します。

#### 必要なすべてのクラスデータを使用可能にする

同形持続されるクラスデータはすべて、クラスの持続性を実装するために使用される大域関数 `rwSaveGuts` と `rwRestoreGuts` にアクセスできなければなりません。

そのクラスのオブジェクトを再度作成するために必要な情報だけが、`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` を使用してください。

  `RWDECLARE_PERSISTABLE` は、`rw/edefs.h` に入っているマクロです。これを使用するには、次の行をヘッダファイル (*.h) に追加してください。

        #include <rw/edefs.h>
        RWDECLARE_PERSISTABLE(YourClass)

  `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);

- 単一のテンプレートパラメータ T を持つテンプレート化されたクラスの場合は、`RWDECLARE_PERSISTABLE_TEMPLATE` を使用してください。

  `RWDECLARE_PERSISTABLE_TEMPLATE` は、`rw/edefs.h` にもあります。これを使用するには、次の行をヘッダファイル (*.h) に追加してください。

        #include <rw/edefs.h>
        RWDECLARE_PERSISTABLE_TEMPLATE(YourClass)

  `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);

- 2 〜 4 個までのテンプレートパラメータを持つテンプレート化されたクラスの場合は、`rw/edefs.h` から次のいずれかのマクロを使用してください。

        // 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)

> 注 - テンプレート化されたクラスに型名でないテンプレートパラメータがある場合は、同形持続させることができません。

- 5 個以上のテンプレートパラメータを持つテンプレート化されたクラスを持続させる必要がある場合は、`RWDELARE_PERSISTABLE_TEMPLATE_n` に追加マクロを作成することができます。このマクロは、`rw/edefs.h` に入っています。

#### RWDEFINE_PERSISTABLE を 1 つのソースファイルに追加する

大域格納演算子と抽出演算子を宣言した後には、それらを定義しなければなりません。Tools.h++ には、RWvistream、RWvostream、RWFile との間で格納と抽出を行うための大域関数 `operator<<` と `operator>>` を定義するコードをソースファイル2に追加するためのマクロがあります。`RWDEFINE_PERSISTABLE` マクロは、同形持続性を実装し、大域持続性関数 `rwSaveGuts` と `rwRestoreGuts` を各自のクラスに呼び出す大域関数 `operator<<` と `operator>>` を自動的に作成します。`rwSaveGuts` と `rwRestoreGuts` については後で詳しく説明します。

ここでも、どのマクロを使用するかは、クラスがテンプレート化されているかどうか、もしされている場合はそのクラスが必要とするパラメータの数によって決まります。

- テンプレート化されていないクラスの場合は、`RWDEFINE_PERSISTABLE` を使用してください。

  `RWDEFINE_PERSISTABLE` は、`rw/epersist.h` に入っているマクロです。これを使用するには、次の行を 1 つのソースファイル (`*.cpp` または `*.C`) だけに追加してください。

        #include <rw/epersist.h>
        RWDEFINE_PERSISTABLE(YourClass)

  `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)

- 単一のテンプレートパラメータ T を持つテンプレート化されたクラスの場合は、`RWDEFINE_PERSISTABLE_TEMPLATE` を使用してください。

  `RWDEFINE_PERSISTABLE_TEMPLATE` も `rw/epersist.h` に入っています。これを使用するには、次の行を 1 つのソースファイル (`*.cpp` または `*.C`) だけに追加してください。

        #include <rw/epersist.h>
        RWDEFINE_PERSISTABLE_TEMPLATE(YourClass)

  `RWDEFINE_PERSISTABLE_TEMLATE(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)

- 2 〜 4 個までのテンプレートパラメータを持つテンプレート化されたクラスの場合は、`rw/epersist.h` から次のいずれかのマクロを使用してください。

      // 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)

> 注 - テンプレート化されたクラスに型名でないテンプレートパラメータがある場合は、同形持続させることができません。

- 5 個以上のテンプレートパラメータを持つテンプレート化されたクラスを持続させる必要がある場合は、`RWDELARE_PERSISTABLE_TEMPLATE_n` に追加マクロを作成することができます。このマクロは、`rw/epersist.h` に入っています。

#### 問題の可能性を確かめる

必要なデータをアクセス可能にして、同形持続性に必要な大域関数を宣言して定義した後には、作業内容を見直して、次の 2 つの問題がないかを確認する必要があります。

1. マクロ `RWDECLARE_PERSISTABLE_TEMPLATE` と `RWDEFINE_PERSISTABLE_TEMPLATE` を使用して、型名でないテンプレートパラメータを持つテンプレート化されたクラスを持続させることはできません。RWTBitVec<size> などの型名でないテンプレートパラメータを持つテンプレートは、同形持続させることができません。

2. 次の大域演算子のいずれかを定義して、`RWDEFINE_PERSISTABLE` マクロを使用すると、コンパイラにとってあいまいであるというエラーが発生します。

           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);

このコンパイラエラーは、異なる定義の演算子とともに `RWDEFINE_PERSISTABLE` を使用することによって、演算子を 2 回定義しているために発生します。つまり、コンパイラは、どの演算子定義を使用すべきかがわかりません。この場合は、次の 2 つの選択肢があります。

- `YourClass` に定義した大域関数 `operator<<` と `operator>>` を削除して、`RWDEFINE_PERSISTABLE(YourClass)` によって生成された演算子でそれらを置換します。
- `YourClass` の大域関数 `operator<<` と `operator>>` を、`rw/epersist.h` に入っている `RWDEFINE_PERSISTABLE` マクロの内容に従って修正します。

#### rwSaveGuts と rwRestoreGuts を定義する

ここで、1 つのソースファイルだけに、大域関数 `rwSaveGuts` と `rwRetoreGuts` を追加する必要があります。これらは、クラスの内部状態を保存して復元するために使用されます。これらの関数は、263 ページの「RWDECLARE_PERSISTABLE をヘッダファイルに追加する」と 266 ページの「RWDEFINE_PERSISTABLE を 1 つのソースファイルに追加する」の説明に従って宣言されて定義された、`operator<<` と `operator>>` によって呼び出されます。

> 注 - 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) {/*...*/}

- 単一のテンプレートパラメータ 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` と `rwSaveGuts` を定義してください。

関数 `rwSaveGuts` は、持続に必要な各クラスメンバーの状態を、RWvostream か RWFile に保存します。クラスのメンバーが持続可能な場合 (表 13-2 を参照)、必要なクラスメンバーが にアクセス可能な場合は、`operator<<` を使用してクラスメンバーを保存することができます。

関数 `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_;
      };

次の 2 つの節では、テンプレート化されていないクラスの `rwSaveGuts` 関数と `rwRestoreGuts` 関数を作成する方法を説明します。ただし、この説明は、テンプレート化されたクラス用に作成されたテンプレート化 `rwSaveGuts` と `rwRestoreGuts` にも適用されます。

#### rwSaveGuts の作成の指針

次の大域多重定義関数は、バイナリファイル (クラス RWFile を使用)、または仮想出力ストリーム (RWvostream を使用) のいずれかに `YourClass` オブジェクトの内部状態を保存する義務があります。

これにより、オブジェクトをいつでも復元することができます。

      rwSaveGuts(RWFile& f, const YourClass& t)
      rwSaveGuts (RWvostream& s, const YourClass& t)

作成する `rwSaveGuts` 関数は、継承元のクラスのメンバーを含めて、各メンバーの状態を `YourClass` に保存しなければなりません。

関数の作成方法は、メンバーデータの型によって異なります。

- C++ 基本型 (`int`、`char`、`float` など)、または RWCollectable を含むほとんどの Rogue Wave クラスのいずれかであるメンバーデータを保存するには、多重定義された挿入演算子 (`operator<<`) を使用してください。

- 非 RWCollectable オブジェクトへのポインタであるメンバーの保存には、若干の技術が必要な場合があります。これは、ポインタがオブジェクトをまったく指さない可能性があるためです。空ポインタの可能性を処理する方法のひとつとして、あるポインタが有効なオブジェクトを指すかどうかを調べるという方法があります。ポインタが有効な場合は、ブール値の `true` を保存してから、間接参照されるポインタを保存します。ポインタが無効な場合は、ブール値の `false` を保存しますが、ポインタは保存しません。

  ポインタを復元する場合、`rwRestoreGuts` はまずブール値を復元します。ブール値が `true` であれば、`rwRestoreGuts` は有効なポインタを復元します。ブール値が `false` であれば、`rwRestoreGuts` はポインタを空に設定します。

- RWCollectable から派生したオブジェクトへのポインタの保存はこれよりも簡単です。この場合でも、ポインタが空である可能性がありますが、次のコードを使用してポインタを保存すると、`NULL` ポインタは自動的に検出されます。

         RWvostream& operator<<(RWvostream&, const RWCollectable*);

これらの指針を使用すると、次のように、例のクラス `Gut` の `rwSaveGuts` 関数を作成することができます。

    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 を使用)、または仮想出力ストリーム (RWvostream を使用) のいずれかから、`YourClass` オブジェクトの内部状態を復元する義務があります。

      rwRestoreGuts(RWFile& f, YourClass& t)
      rwRestoreGuts(RWvostream& s, YourClass& t)

作成する `rwRestoreGuts` 関数は、継承元のクラスのメンバーを含めて、`YourClass` の各メンバーの状態を復元しなければなりません。関数は、メンバーデータを保存された順序で復元しなければなりません。

関数の作成方法は、メンバーデータの型によって異なります。

- C++ 基本型 (`int`、`char`、`float` など)、または RWCollectable を含むほとんどの Rogue Wave クラスのいずれかであるメンバーデータを復元するには、多重定義された抽出演算子 (`operator>>`) を使用してください。

- 非 RWCollectable オブジェクトへのポインタであるメンバーの復元には、若干の技術が必要な場合があります。これは、保存されたポインタがオブジェクトをまったく指さない可能性があるためです。ただし、前の節で説明したように、`rwSaveGuts` がブール値フラグを保存してからポインタを保存している場合は、`rwRestoreGuts` での有効なポインタと `NULL` ポインタの保存は比較的簡単です。

  メンバーが、互換性のある `rwSaveGuts` によって保存されたと想定した場合、ポインタを復元するとき、`rwRestoreGuts` はまずブール値を復元します。ブール値が `true` であれば、`rwRestoreGuts` は有効なポインタを復元します。ブール値が `false` であれば、`rwRestoreGuts` はポインタを空に設定します。

- RWCollectable から派生したオブジェクトへのポインタの復元はこれよりも簡単です。この場合でも、ポインタが空である可能性がありますが、次のコードを使用してポインタを復元すると、`NULL` ポインタは自動的に検出されます。

          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：単純持続性とポインタ」では、ポインタを含むコレクションに単純持続性を実装するコードの例をいくつか説明しました。それらの例では、単純持続性が元のコレクションの構造をどうして維持できないかを説明しました。

次の例では、244 ページの「例 2：単純持続性とポインタ」で設定したコレクションに同形持続性を実装しています。`Team` には 3 つの `Developers` が含まれます。図 13-7 は、元の `Team` コレクションと、同形持続性によって保存して復元した後の `Team` コレクションの構造を示しています。

保存されるコレクション (`team1`) | 復元されたコレクション (`team2`)

##### 図 13-7 同形持続性

コードを読んで、他の `Developers` を指す `Developer::alias_` メンバーがどのように保存されて復元されるかに注意してください。`Developer::name_` を保存した後、`Developer` の `rwSaveGuts` 関数が、`alias_` がメモリ内の `Developer` を指すかどうかを確認することがわかります。指さない場合、`rwSaveGuts` はブール値 `false` を格納して、その `alias_` が `NULL` ポインタであることを示します。`alias_` が `Developer` を指す場合、`rwSaveGuts` はブール値 `true` を格納します。この後、`rwSaveGuts` は、`alias_` の指している `Developer` の値を最終的に格納します。

このコードは、新しい `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 は、多相持続性を使用するクラスを示しています。

#### 表 13-3 多相持続性クラス

カテゴリ | 記述
--- | ---
RWColletable (Smalltalk 式) クラス | RWCollectableDate, RWCollectableString...
RWCollection クラス (RWCollectable から派生) | RWBinaryTree, RWBag...

### 演算子

RWCollectable から継承する多相オブジェクトの格納と検索は、Tools.h++ クラスライブラリの強力で柔軟な機能です。他の持続性機構と同様、多相持続性は、多重定義された抽出演算子と挿入演算子 (`operator<<` と `operator>>` 多相持続) を使用します。これらの演算子を多相持続性で使用すると、同形保存され復元されたオブジェクトだけでなく、未知の型のオブジェクトも復元することができます。

多相持続性機構は次の演算子を使用します。

- RWCollectable オブジェクトへの参照を保存する演算子

      Rwvostream&  operator<<(RWvostream&, const RWCollectable&);
      RWFile&      operator<<(RWFile&,     const RWCollectable&);

  RWCollectable 派生オブジェクトはそれぞれ、そのオブジェクトのクラスを固有に識別するクラス ID によって同形保存されます。

- RWCollectable ポインタを保存する演算子

      Rwvostream&  operator<<(RWvostream&, const RWCollectable*);
      RWFile&      operator<<(RWFile&,     const RWCollectable*);

  オブジェクトへの各ポインタは、そのオブジェクトのクラスを固有に識別するクラス ID によって同形保存されます。`NULL` ポインタも保存することができます。

- 既存の RWCollectable オブジェクトを復元する演算子

      Rwvistream&  operator>>(RWvistream&, RWCollectable&);
      RWFile&      operator>>(RWFile&,     RWCollectable&);

  RWCollectable 派生オブジェクトはそれぞれ同形復元されます。持続性機構は、オブジェクトとともに格納されたクラス ID を調べて、実行時にオブジェクトの型を判別します。

- RWCollectable オブジェクトへのポインタを復元する演算子

      Rwvistream&  operator>>(RWvistream&, RWCollectable*&);
      RWFile&      operator>>(RWFile&,     RWCollectable*&);

  RWCollectable から派生した各オブジェクトは同形復元され、ポインタ参照は、復元されたオブジェクトを指すように更新されます。持続性機構は、オブジェクトとともに格納されたクラス ID を調べて、実行時にオブジェクトの型を判別します。復元されたオブジェクトは、ヒープから割り当てられるため、これらのオブジェクトの処理を終えたら、ユーザーがそれらを削除しなければなりません。

### 多相持続性を使用するようにクラスを設計する

多相オブジェクトのポインタの関係を復元する機能は、基底クラス RWCollectable の特性であることに注意してください。多相持続性は、各ユーザーが作成するクラスを含む、RWCollectable から継承するすべてのオブジェクトで使用することができます。第 14 章「RWCollectable クラスの設計」では、RWCollectable から継承することによってユーザーが作成したクラスで、多相持続性を実装する方法を説明します。

### 多相持続性の例

この多相持続性の例には、2 つの異なるプログラムが含まれます。最初の例では、コレクションの内容を標準出力 (`stdout`) に多相保存します。2 番目の例では、保存されたコレクションの内容を標準入力 (`stdin`) から多相復元します。持続性を使用して、2 つの異なるプロセス間でオブジェクトを共有できることを示すために、例を 2 つに分けました。

最初の例をコンパイルして実行する場合、出力はファイルに保存されるため、オブジェクトになります。ただし、最初の例の出力を 2 番目の例にパイプすることができます。

      firstExample | secondExample

#### 例 1：多相保存

この例では、空のコレクションを作成し、そのコレクションにオブジェクトを挿入して、そのコレクションを標準出力に多相保存します。

例 1 では、同じオブジェクトの 2 つのコピーと、他の 2 つのオブジェクトが含まれるコレクションを作成して保存していることに注意してください。これらの 4 つのオブジェクトの型は 3 つあります。例 1 がコレクションを保存し、例 2 がそのコレクションを復元するとき、次のことがわかります。

- コレクションの構造が維持される

- コレクションを復元する処理では、オブジェクトを復元する前にそのオブジェクトの型がわからない

例 1 は次のとおりです。

    #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;

この文は、多重定義された抽出演算子を使用して、最初の例で保存されたコレクションを collection2 に多相復元します。

持続性は次のようにして起こります。入力ストリーム `istr` から `collection2` に復元された、RWCollectable から派生されたオブジェクトへの各ポインタのそれぞれに対して、抽出演算子 `operator>>` は、各種の多重定義された抽出演算子と持続性関数を呼び出します。各 RWCollectable 派生オブジェクトのポインタのそれぞれに対して、`collection2` の抽出演算子は、次のことを行います。

- ストリーム `istr` を読み取って、RWCollectable から派生されたオブジェクトの型を発見します。

- ストリーム `istr` を読み取って、指されている RWCollectable 派生オブジェクトがすでに復元されて、復元テーブルで参照されているかどうかを確認します。

  - RWCollectable 派生オブジェクトがまだ復元されていない場合、抽出演算子は、ポインタを作成して、正しい型のオブジェクトをヒープから作成して、作成されたオブジェクトをストリームから読み取ったデータで初期化します。さらに、新しいオブジェクトのアドレスによってポインタを更新して、最後にそのオブジェクトへの参照を復元テーブルに保存します。

  - RWCollectable 派生オブジェクトがすでに復元されている場合、抽出演算子はポインタを作成して、ストリームからオブジェクトへの参照を読み取ります。さらに、その参照を使用して、オブジェクトのアドレスを復元テーブルから読み取り、このアドレスによってポインタを更新します。

- 最後に、復元されたポインタがコレクションに挿入されます。

持続機構の実装については、後の「例 2 を再検討する」で詳しく説明します。ただし、異種コレクション (RWCollection にもとづいてものでなければならない) を復元するとき、復元処理は、復元対象のオブジェクトの型を知らないことに注意する必要があります。したがって、常にヒープからオブジェクトを割り当てなければなりません。つまり、ユーザーが復元された内容を削除しなければならないということです。これは、例の終わりにある式 `collection2.clearAndDestroy` で起こります。

次に、例を示します。

    #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"

図 13-8 は、最初の例で作成されて、2 番目の例で復元されたコレクションを示しています。保存されたコレクションと復元されたコレクションで、メモリーマップとデータ型の両方が同じであることに注意してください。

保存されるコレクション (`collection1`) | 復元されたコレクション (`collection2`)

##### 図 13-8 多相持続性

#### 例 2 を再検討する

2 番目の例をもう一度見直すと、多相持続性を実装するために使用された機構を確認することができます。次の式は多重定義された抽出演算子を呼び出します。

      istr >> collection2;

この多重定義された抽出演算子は次のとおりです。

      RWvistream& operator>>(RWvistream& str, RWCollectable& obj);

この抽出演算子は、オブジェクトの `restoreGuts()` 仮想関数を呼び出すように作成されています。この場合、オブジェクト `obj` は順序付きコレクションであり、そのバージョンの `restoreGuts()` は、次のコードを繰り返し呼び出すように作成されています。

      RWvistream& operator>>(RWvistream&, RWCollectable*&);

このコードは、コレクションの各メンバーについて 1 回ずつ呼び出されます3。2 番目の引数は、ただの参照ではなく、ポインタへの参照であることに注意してください。このバージョンの多重定義 `operator>>` は、ストリームを調べ、ストリーム上のオブジェクトの種類を判別し、その型のオブジェクトをヒープから割り当て、それをストリームから復元して、最後に、そのオブジェクトへのポインタを返します。この `operator>>` は、すでにあるオブジェクトへの参照を見つけると、単に古いアドレスを返します。これらのポインタは、順序付きコレクションの `restoreGuts()` によって、コレクションに挿入されます。

多相持続性機構に関するこのような詳細は、第 14 章「RWCollectable クラスの設計」で説明するように、独自の多相持続可能クラスを設計するときに特に重要です。また、このようなクラスを処理する場合は、Smalltalk 式コレクションクラスを復元するときに、復元されるオブジェクトの型がまったくわからないことに注意してください。したがって、復元処理は、これらのオブジェクトを常にヒープから割り当てなければなりません。つまり、ユーザーが復元された内容を削除しなければならないということです。この例は、両方の多相持続性の例の終わりで起こっています。

#### 使用する持続演算子を選択する

2 番目の例では、持続演算子は、次のように、コレクションを RWCollectable への参照に復元していました。

      Rwvistream&  operator>>(RWvistream&, RWCollectable&);

持続演算子は、次のように、RWCollectable への参照へのポインタには復元していません。

      Rwvistream&  operator>>(RWvistream&, RWCollectable*&);

コレクションは、次のように、スタックに割り当てられていました。

      RWpistream istr(cin);
      RWOrdered collection2;
      istr >> collection2;
      ...
      collection2.clearAndDestroy();

次のように、`operator>>(RWvistream&, RWCollectable*&)` を使って、コレクションにメモリーを割り当てているのではありません。

      RWpistream istr(cin);
      RWOrdered* pCollection2;
      istr >> pCollection2;
      ...
      collection->clearAndDestroy();
      delete pCollection2;

復元中のコレクションの型がわかっている場合、通常は、次のように、ユーザー自身がその型にメモリーを割り当て、復元する方がよいため、このような選択が行われます。

      Rwvistream&  operator>>(RWvistream&, RWCollectable&);

参照演算子を使用すると、持続性機構でオブジェクトの型を判別し、RWFactory に型を割り当てさせるために必要な時間を省くことができます (358 ページの「RWFactory に関する注意」)。また、ユーザー自身がコレクションを割り当てると、各自の必要に合わせて割り当てを調整することができます。たとえば、コレクションクラスの初期容量を設定することができます。

## その他の注意事項

持続性は便利なものですが、注意を要する場合もあります。次に、オブジェクトに対して持続性機構を使用するときに注意すべき 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&)

`elmer` はまだ保存されていないため、`elmer` の値は `file` に保存され、この値は同形保存テーブルに追加されます。ただし、`elmer` にはメンバー `elroy_` と `elroyPtr_` があり、`elmer` の保存の一部として保存しなければなりません。`elmer` のメンバーは、`rwSaveGuts(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;

この文は、抽出演算子 `operator>>(RWFile&, const Elmer&)` を呼び出します。`elmer2` はまだ復元されていないため、`elmer2` の値が `file` から抽出されて、同形復元テーブルに追加されます。`elmer2` の値を抽出するには、メンバー `elroy_` と `elroyPtr_` が抽出されなければなりません。`elmer2` のメンバーは、`rwRestoreGuts(RWFile&, Elmer&)` で抽出されます。

関数 `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_;
      }

前述のように `rwRestoreGuts` と `rwSaveGuts` を訂正すれば、同形保存テーブルと同形復元テーブルは、`Elmer::elroy_` のアドレスを使用して、`Elmer::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` のアドレスは同形保存テーブルに保存されます。次に保存される項目は `godzilla.mothra_` です。このアドレスは同じ内部保存テーブルに保存されます。

ここで問題になるのは、コンパイラによっては、`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_ は、この時点で異なるアドレスを持つ
      };

`Godzilla` がこのように作成されると、`mothra_` は `godzilla` の前から移動して、`godzilla` と混同されることがなくなります。変数 `wins_` は `int` 型であり、単純持続性によって保存され、同形保存テーブルには格納されません。

クラスとそのメンバーが同じアドレスを持つという問題を解決する 2 番目の方法では、同形持続可能なメンバーを、値ではなく、ポインタとして挿入します。`Godzilla` は次のようになります。

      struct Godzilla {
        Mothra* mothraPtr_;// mothraPtr_ は異なるアドレスを指す
        int     wins_;
      };

この方法では、`mothraPtr_` は `godzilla` 以外のアドレスを指すため、やはり混同は避けられます。

### ソートされた 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 が必要とする適切なコードにリンクするために必要な情報を、リンカーに与えることができます。

1 C++ のテンプレート機構は、この機能を妨げます。しかし、ある位置で保存された複数のポインタは、別の位置に挿入されると問題になることが多いため、この制約はむしろ望ましいものです。

2 テンプレートクラスの場合、一部のコンパイラでは、ソースファイルが、`RWDECLARE_PERSISTABLE` が使用されたヘッダファイルと同じ基底名を持たなければならない場合があります。

3 実際には、Smalltalk 式コレクションクラスは非常に似ているため、どれも RWCollection から継承された 同じバージョンの `RestoreGuts()` を共有しています。