Previous Next Contents Index


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

持続性

13



持続性とは

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

持続性のレベル

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

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

用語に関する注意

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

説明をわかりやすくするために、これらの入力クラスと出力クラスをすべてストリームと呼ぶことにします。RWFile に対して、RWvostreamRWvistream を使用した場合の長所と短所の説明については、第 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 へのポインタだけを含むことに注意してください。

単純持続性を使用して DevelopersTeams を保存、復元する多重定義された抽出演算子と挿入演算子が記述されているものと仮定します。ここでは、説明を簡単にするために、この仮定を示すコードの例を省略します。

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

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

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

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

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

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

  5. rwSaveGutsrwRestoreGuts を定義します。

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

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

そのクラスのオブジェクトを再度作成するために必要な情報だけが、rwSaveGutsrwRestoreGuts にアクセスできなければならないことに注意してください。他のデータは限定公開または非公開の状態にしておくことができます。

クラスの限定公開メンバーまたは非公開メンバーをアクセス可能にするには、いくつかの方法があります。

最初に、クラスは rwSaveGutsrwRestoreGuts のフレンドになることができます。

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++ には、これらの宣言を簡単に追加できるようにするためのマクロがいくつかあります。どのマクロを選択するかは、クラスがテンプレート化されているかどうかによって異なります。テンプレート化されている場合は、テンプレート化されたパラメータがいくつあるかによって異なります。


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

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

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

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


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

問題の可能性を確かめる

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

  1. マクロ RWDECLARE_PERSISTABLE_TEMPLATERWDEFINE_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 つの選択肢があります。

rwSaveGuts と rwRestoreGuts を定義する

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


注 - 272 ページの「関数 rwSaveGuts と rwRestoreGuts を作成する」には、大域関数 rwSaveGutsrwRestoreGuts の作成方法に関する指針が示してあります。
関数 rwSaveGuts は、持続に必要な各クラスメンバーの状態を、RWvostreamRWFile に保存します。クラスのメンバーが持続可能な場合 (表 13-2 を参照)、必要なクラスメンバーが にアクセス可能な場合は、operator<< を使用してクラスメンバーを保存することができます。

関数 rwRestoreGuts は、持続性に必要な各クラスメンバーの状態を、RWvistreamRWFile から復元します。クラスのメンバーが持続可能な型である場合と、クラスのメンバーが rwRestoreGuts にアクセス可能な場合は、operator>> を使用してクラスメンバーを復元することができます。

関数 rwSaveGuts と rwRestoreGuts を作成する

次の 2 つの節では、大域関数 rwSaveGutsrwRestoreGuts を作成するための指針について説明します。これらの指針を示すために、次のクラスを使用します。

class Gut {
  public:
    int                   fundamentalType_;
    RWCString             aRogueWaveObject_;
    RWTValDlist           anotherRogueWaveObject_;
    RWCollectableString   anRWCollectable_;
    RWCollectableString*  pointerToAnRWCollectable_;
    Gut*                  pointerToAnObject_;
};
次の 2 つの節では、テンプレート化されていないクラスの rwSaveGuts 関数と rwRestoreGuts 関数を作成する方法を説明します。ただし、この説明は、テンプレート化されたクラス用に作成されたテンプレート化 rwSaveGutsrwRestoreGuts にも適用されます。

rwSaveGuts の作成の指針

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

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

rwSaveGuts(RWFile& f, const YourClass& t)
rwSaveGuts (RWvostream& s, const YourClass& t)
作成する rwSaveGuts 関数は、継承元のクラスのメンバーを含めて、各メンバーの状態を YourClass に保存しなければなりません。

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

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

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

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 の各メンバーの状態を復元しなければなりません。関数は、メンバーデータを保存された順序で復元しなければなりません。

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

ユーザー設計クラスの同形持続性

244 ページの「例 2:単純持続性とポインタ」では、ポインタを含むコレクションに単純持続性を実装するコードの例をいくつか説明しました。それらの例では、単純持続性が元のコレクションの構造をどうして維持できないかを説明しました。

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

保存されるコレクション (team1)

復元されたコレクション (team2)

図 13-7 同形持続性
コードを読んで、他の Developers を指す Developer::alias_ メンバーがどのように保存されて復元されるかに注意してください。Developer::name_ を保存した後、DeveloperrwSaveGuts 関数が、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_ を復元すると、DeveloperrwRestoreGuts はブール値を復元して、Developer::alias_ がメモリ内の Developer を指すかどうかを判断します。ブール値が true の場合、alias_Developer を指すため、rwRestoreGuts はその Developer オブジェクトを復元します。さらに、rwRestoreGuts は、復元された Developer を指すように alias_ を更新します。

前述の Developer.alias_ の 同形持続性の格納処理と検索処理は、TeamDeveloper ポインタに適用することもできます。

次にコードを示します。

#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 の特性であることに注意してください。多相持続性は、各ユーザーが作成するクラスを含む、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 つの型のオブジェクトが格納されていることに注意してください。つまり、RWCollectableDateRWCollectableInt、2 つの RWCollectableString です。同じ RWCollectableStringgeorge が、collection に 2 回挿入されます。

例 2:多相復元

2 番目の例は、最初の例で多相保存されたコレクションを、多重定義された抽出演算子によって、どのように読み取り復元するかを示しています。

Rwvistream&  operator>>(RWvistream&, RWCollectable&);
この例では、持続はプログラムが次の文を実行すると起こります。

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

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

持続機構の実装については、後の「例 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 の点を示します。

オブジェクトを保存する場合は、必ずそのオブジェクトを値によって保存してから、ポインタによって保存する

オブジェクトの同形持続性の場合でも多相持続性の場合でも、あるオブジェクトを必ず値によってストリーム出力してから、ポインタによってストリーム出力しなければなりません。値とその値へのポインタを含むクラスを設計する場合、そのクラスのメンバー関数 saveGutsrestoreGuts は、必ず値を保存または復元してから、ポインタを保存または復元しなければなりません。

次に、同形持続性を使用するクラスを作成して、そのクラスのオブジェクトをインスタンス化して、これらのオブジェクトを同形持続しようとする例を示します。この例は正常に実行されません。理由については、説明を参照してください。

  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 例外。
  /* ... */

このコードでは、次の文が elmerfile に同形保存します。

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_;
}
前述のように rwRestoreGutsrwSaveGuts を訂正すれば、同形保存テーブルと同形復元テーブルは、Elmer::elroy_ のアドレスを使用して、Elmer::elroyPtr_ を必要に応じて更新することができます。

要約:同じクラス内に、ある値とその値を指すポインタの両方が存在する可能性があるため、rwSaveGutsrwRestoreGuts の各メンバー関数は、常に値を処理してからポインタを処理するように定義してください。

同じアドレスを持つ異なるオブジェクトを保存しない

同じアドレスを持つ可能性がある異なるオブジェクトを同形保存しないように注意してください。同形持続性と多相持続性で使用される内部テーブルは、オブジェクトのアドレスを使用して、オブジェクトがすでに保存されているかどうかを判別します。

次の例は、GodzillaMothra を同形持続可能にするための処理がすべて実行されたものと想定しています。

class Mothra {/* ... */};
RWDEFINE_PERSISTABLE(Mothra)

struct Godzilla {
  Mothra  mothra_;
  int     wins_;
};
RWDEFINE_PERSISTABLE(Godzilla)
/*... */
Godzilla  godzilla;
/* ... */
stream << godzilla;
/* ... */
stream >> godzilla;       // 復元に異常がある可能性がある
godzilla を保存すると、godzilla のアドレスは同形保存テーブルに保存されます。次に保存される項目は godzilla.mothra_ です。このアドレスは同じ内部保存テーブルに保存されます。

ここで問題になるのは、コンパイラによっては、godzillagodzilla.mothra_ が同じアドレスを持つという点です。godzilla の復元時に、godzilla.mothra_ は値として保存され、godzillagodzilla.mothra_ への参照として保存されます。godzillagodzilla.mothra_ のアドレスが同じである場合、抽出演算子は godzillagodzilla.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 を使用することができません。たとえば、RWCollectableStringRWCollectableDates を同じ 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() を共有しています。


Previous Next Contents Index