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

14.4 同形永続性

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

表 2 に示されている Tools.h++ クラスは、どれも同形永続性を使って、保存し復元できます。さらに、14.4.3 節に説明されている技法に従って、同形永続性をクラスに追加することもできます。

Tools.h++ の実装では、ポインタをテンプレート化した型の同形永続性には対応していないことに注意してください。例えば、RWTValDlist< int*> の保存と復元には対応していません。[1] この場合は、代わりに RWTPtrDlist<int> を使うことをお勧めします。

表 2. 同形永続性を持つクラス

分類
説明
標準 C++ ライブラリを基本にした Rogue Waveのコレクションクラス
RWTValDeque, RWTPtrMap, …
RWCollectable (Smalltalk_like) クラス
RWCollectableDate, RWCollectableString… RWCollectable クラスは多形永続性も備えていることに注意してください (1.5 節を参照)
RWCollection クラスから派生した RWCollection クラス
RWBinaryTree, RWBag, …
Rogue Wave Tools.h++ バージョン6.x のテンプレート化されたコレクション
RWTPtrDlist, RWTValDlist, RWTPtrSlist, RWTValSlist, RWTPtrOrderedVector, RWTValOrderedVector, RWTPtrSortedVector, RWTValSortedVector, RWTPtrVector, RWTValVector

14.4.1 同形永続性と単純永続性

同形永続性と単純永続性の違いを示す図を 2 つ見てみましょう。図 2
では、同じオブジェクトを指すポインタを複数持つコレクションが、単純永続性でストリームに格納され、復元されています。図
2
ではコレクションが格納され復元されるとき、ポインタがそれぞれ別のオブジェクトを指していることに気づくでしょう。これとは対照的に同形永続性を持つ同じコレクションでは、図
3
に示されているように、復元されたポインタは元のコレクションと同様にすべて同一オブジェクトを指しています。

図 2. 単純永続性を持つコレクションの格納と復元

image6

image7

図 3. 同形永続性を持つコレクションの保存と復元

image8

image9

図 4 では、単純永続性を用いて循環リンクリストの格納と復元を試みます。図に示されているように、単純永続性を使って循環リンクリストを保存しようとすると、無限のループを引き起こしてしまいます。

単純永続性の機構は、参照先の各オブジェクトのコピーを作成し、そのオブジェクトをストリームに保存します。ただし、単純永続性の機構は、どのオブジェクトがすでに保存されているかは記憶しません。単純永続性の機構には、ポインタを見つけたとき、そのポインタが指しているオブジェクトがすでに保存されているかどうかを判断する手段がありません。そのため、循環リンクリストでは、単純永続性の機構は、永久的にリストを循環しながら同じオブジェクトを何度も保存します。

しかし、図 5 に示されているように、同形永続性を使うと循環リンクリストを保存することができます。同形永続性の機構は、表を使ってすでに保存されているポインタを記録します。同形永続性の機構では、まだ保存されていないオブジェクトを指すポインタを見つけると、オブジェクトのデータをコピーし、ポインタではなく、そのオブジェクトのデータをストリームに保存します。そして、「保管表」にポインタを記録します。その後、同形永続性の機構は同じオブジェクトを指すポインタを見つけると、オブジェクトデータをコピーしたり保存したりする代わりに、保管表の参照をポインタに保存します。

同形永続性の機構はストリームからオブジェクトを指すポインタを復元するとき、「復元表」を使って、逆の処理を行います。同形永続性の機構が復元されていないオブジェクトを指すポインタを見つけると、ストリームからのデータでオブジェクトを再作成し、復元されているポインタを再作成されたオブジェクトを指すように変更します。この機構は、ポインタを復元表に記録します。その後、同形永続性の機構がすでに復元されているポインタの参照を見つけると、復元表の参照を調べて、表で参照されているオブジェクトを指すように復元されたポインタを更新します。

図 4 では、単純永続性を用いて循環リンクリストの格納と復元を試みます。

image10
image11

図 5. 同形永続性を持つ循環リンクリストの保存と復元

image12
image13

14.4.2 Tools.h++ クラスの同形永続性

次の例は、整数型の RWCollectable のテンプレート化されたコレクションである RWTPtrDlist<RWCollectableInt> の同形永続性を示したものです。RWTPtrDlist は、テンプレート化されており、同形永続性を使って値へのポインタ参照をメモリに格納する、参照ベースの二重リンクリストです。

int は単純永続性を持つため、この例では、int の代わりに RWCollectableInt を使用します。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" は
    // それぞれ同じ位置を指す

  RWTPtrDlist<RWCollectableInt> dlist2;
  {
    RWFile            f("dlist.dat");

    f >> dlist2;
      // f から dlist2 を復元する
      // dlist2 は、value 1 の同じ RWCollectableInt を
      // 指すポインタを 2 つ含む
      // ただし、この RWCollectableInt は "one" が
      // 指している値と
      // は同じアドレスにない
  }

  // dlist1 と dlist2 がメモリ内でどのように見えるかについては、
  // 図 6 を参照

  assert(dlist2[0] == dlist2[1] && (*dlist2[0]) == *one);
    // dlist2[0] と dlist2[1] は同じ位置を指し、
    // その位置は同じ値である "one" を持つ
  delete dlist2[0];
  delete one;
    // オブジェクトの割り当てと削除はユーザの責任。
    // テンプレート化されたコレクションのメンバ関数
    // clearAndDestroy() は与えられたポインタが
    // 一度だけ削除されていることを確認しない。
    // この場合は、共有ポインタは手作業で
    // 削除する必要がある

  return 0;

図 6. 同形永続性で RWTPtrDlist<RWCollectableInt> を保存と復元した後

image14

14.4.3 同形永続性を持つように独自のクラスを設計する場合

表 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、あるいは同等のマクロを使用しなければなりません。これについて詳しくは、第 14.4.3.2 節14.4.3.3 節を参照してください。
  • クラス T のインスタンスを再作成するのに必要なデータはすべて、グローバルにアクセス可能でなければなりません (アクセス関数を持つ必要がある)。このデータをアクセス可能にできない場合は、同形永続性を実装できません。これについて詳しくは、14.4.3.1 節を参照してください。

クラス T を標準 C++ ライブラリのコンテナまたは標準 C++ ライブラリベースのコレクションに格納する場合は、operator<(const T&, const T&) と operator==(const T&, const T&) を実装する必要があります。詳しくは、11.11 節を参照してください。

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

  1. 必要なクラスデータを使用できるようにする。
  2. ヘッダファイルに RWDECLARE_PERSISTABLE を追加する。
  3. ソースファイルの 1 つに RWDEFINE_PERSISTABLE を追加する。
  4. 発生する可能性のある問題を検討する。
  5. rwSaveGuts と rwRestoreGut を定義する。

14.4.3.1 必要なクラスデータを使用できるようにする

同形永続性を持たせるクラスデータすべてが、クラスに永続性を実装するために使用するグローバル関数
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}
//...
};

クラスのソースコードを変更できない場合は、そのクラスの非公開メンバに同形永続性を持たせることができません。ただし、アクセスする必要があるのは、クラスのメンバすべてではなく、クラスオブジェクトを再作成するために必要なデータだけであることを覚えておいてください。例えば、クラスに実行時に作成される非公開キャッシュがある場合、おそらくそのキャッシュを保存し復元する必要はないはずです。つまり、そのキャッシュが非公開でも、クラスオブジェクトに永続性を持たせるためにそれにアクセスする必要はありません。

14.4.3.2 ヘッダファイルに RWDECLARE_PERSISTABLE を追加する

いったん、必要なクラスデータすべてにアクセス可能であることを判断したら、ヘッダファイルに宣言文を追加しなければなりません。これらの宣言文は、クラスのグローバル関数 operator<< と operator>> を宣言します。グローバル関数は、RwvistreamRwvostreamRWFile への格納と取り出しを許可します。

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 を使用してください。

    // For YourClass<T1,T2>:
    RWDECLARE_PERSISTABLE_TEMPLATE_2(YourClass)
    
    // For YourClass<T1,T2,T3>:
    RWDECLARE_PERSISTABLE_TEMPLATE_3(YourClass)
    
    // For YourClass<T1,T2,T3,T4>:
    RWDECLARE_PERSISTABLE_TEMPLATE_4(YourClass)
    

    テンプレート化されたクラスが型以外のテンプレートパラメータを持つ場合は、同形永続性を持たせることはできません。

  • 5 個以上のテンプレートパラメータを持つテンプレート化されたクラスに永続性を持たせる場合は、RWDEFINE_PERSISTABLE_TEMPLATE_n を使用してください。マクロは、ヘッダーファイル rw/edefs.h にあります。

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

グローバル挿入および抽出演算子を宣言した場合は、それらを定義しなければなりません。Tools.h++ には、ソースファイルにコードを追加して、RwvistreamRwvostream、および RWFile への格納と取り出しのためのグローバル関数 operator<< と operator>> を定義するマクロが用意されています。[2]RWDEFINE_PERSISTABLE マクロは、永続性を持たせるクラスで、同形永続性機能を実行し、グローバル永続性関数 rwSaveGuts and 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_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/epersist.h を使用してください。

    // For YourClass<T1,T2>:
    RWDEFINE_PERSISTABLE_TEMPLATE_2(YourClass)
    
    // For YourClass<T1,T2,T3>:
    RWDEFINE_PERSISTABLE_TEMPLATE_3(YourClass)
    
    // For YourClass<T1,T2,T3,T4>:
    RWDEFINE_PERSISTABLE_TEMPLATE_4(YourClass)
    

    テンプレート化されたクラスが型以外のテンプレートパラメータを持つ場合は、同形永続性を持たせることはできません。

  • 5 個以上のテンプレートパラメータを持つテンプレート化されたクラスに永続性を持たせる場合は、RWDECLARE_PERSISTABLE_TEMPLATE_n を使用してください。マクロは、rw/edefs.h にあります。

13.4.3.4 発生する可能性のある問題を検討する

ここまで、必要なデータをアクセス可能にし、同形永続性に必要なグローバル関数を宣言、定義しました。先に進む前に、ここで発生する可能性のある 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) で生成される演算子で置き換えます。
    • rw/epersist.h にある RWDEFINE_PERSISTABLE マクロの内容をガイドとして使い、YourClass 用のグローバル関数 operator<< と operator>> を変更します。

14.4.3.5 rwSaveGuts と rwRestoreGuts を定義する

グローバル関数 rwSaveGuts と rwRestoreGutsは、ソースファイルの 1 つにだけ追加しなければなりません。これらの関数は、クラスの内部状態を保存し復元するために使用されます。これらの関数は、前述の 14.4.3.2 節14.4.3.3 節でそれぞれ宣言され定義された operator<< と operator>> によって呼び出されます。

注意: 14.4.4 節には、グローバル関数 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
    rwSaveGuts(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) {/* ...*/}
    
  • 2 つ以上のテンプレートパラメータを持つテンプレート化されたクラスの場合は、正しいテンプレートパラメータ数を持つ rwRestoreGuts と rwSaveGuts を定義してください。

関数 rwSaveGuts は、永続性に必要な各クラスメンバの状態を RWvostream または RWFile に保存します。クラスのメンバに永続性があり (表 2 を参照)、rwSaveGuts が必要なクラスメンバにアクセスできる場合は、operator<< を使ってクラスメンバを保存することができます。

関数 rwSaveGuts は、永続性に必要な各クラスメンバの状態を RWvistream または RWFile から復元します。クラスのメンバが永続性のある型で、rwRestoreGuts がクラスのメンバにアクセスできる場合は、operator>> を使ってクラスメンバを復元できます。

14.4.4 関数 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 にも当てはまります。

14.4.4.1 rwSaveGuts 作成のガイドライン

以下の多重定義されたグローバル関数、

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

は、YourClass のオブジェクトの内部状態をバイナリファイル (クラス RWFile を使用) または仮想出力ストリーム (RWvostream) に保存します。これにより、オブジェクトをあとで復元することができます。

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

関数をどのように作成するかは、メンバデータの型によります。

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

  • RWCollectable 以外のオブジェクトを指しているポインタであるメンバを保存するのには、ややコツが必要です。これは、ポインタがオブジェクトをまったく指していない可能性があるからです。nil ポインタの可能性に対処する 1 つの方法は、ポインタが有効なオブジェクトを指しているかどうかを調べることです。ポインタが有効である場合は、論理値 true を保存し、それから間接参照したポインタを保存します。ポインタが無効である場合は、論理値 false は保存しますが、ポインタは保存しません。

    ポインタを復元する場合、rwRestoreGuts は最初に論理値を復元します。論理値が true である場合、rwRestoreGuts は有効なポインタを復元します。論理値が false である場合、rwRestoreGuts はポインタを nil に設定します。

  • RWCollectable から派生したオブジェクトを指しているポインタを保存するのは、より簡単になります。依然、ポインタが nil である可能性がありますが、次の例

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

    を使ってポインタを保存すると、nil ポインタは自動的に削除されます。

これらのガイドラインに従って、以下の例ではクラス Gut に関数 rwSaveGuts を作成します。

void rwSaveGuts(RWvostream& stream, const Gut& gut) {

  // 挿入演算子を使って、基本型オブジェクト、
  // Rogue Wave オブジェクトおよび RWCollectable から
  // 派生したオブジェクトを保存する

  stream
    << gut.fundamentalType_
    << gut.aRogueWaveObject_
    << gut.anotherRogueWaveObject_
    << gut.pointerToAnRWCollectable_;

  // RWCollectable 以外のオブジェクトを指す
  // ポインタを保存するにはコツがいる

  if (gut.pointerToAnObject_ == 0)  // これは nil ポインタか?
    stream << false;                    // その場合は、保存しない
  else {
    stream << true;                       // そうでない場合はポインタは有効
    stream << gut.pointerToAnObject_;  // つまり、保存する
  }
}

void rwSaveGuts(RWFile& stream, const Gut& gut) {
  // この関数の本体は、rwSaveGuts(RWvostream& stream, const Gut& gut)
  // と同一である

14.4.4.2 rwRestoreGuts 作成のガイドライン

以下の多重定義されたグローバル関数、

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

は、YourClass のオブジェクトの内部状態をバイナリファイル (クラス RWFile を使用) または仮想入力ストリーム (RWvistream) から復元します。

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

関数をどのように作成するかは、メンバデータの型によります。

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

  • RWCollectable 以外のオブジェクトを指しているポインタであるメンバを復元するのには、ややコツが必要です。これは、保存されているポインタがオブジェクトをまったく指していなかった可能性があるからです。ただし、rwSaveGuts によってポインタを保存する前に論理フラッグが保存されている場合は、前の節で説明したように、rwRestoreGuts が有効なポインタと nil ポインタを復元するのは比較的簡単です。

    メンバが互換性のある rwSaveGuts で保存されていると仮定すると、ポインタを復元するとき、rwRestoreGuts はまず論理フラッグを復元します。論理値が true である場合、rwRestoreGuts は有効なポインタを復元します。論理値が false である場合、rwRestoreGuts はポインタを nil に設定します。

  • RWCollectable から派生したオブジェクトを指しているポインタを復元するのは、より簡単となります。依然、ポインタが nil である可能性がありますが、次の例

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

    を使ってポインタを復元すると、nil ポインタは自動的に削除されます。

これらのガイドラインに従って、次の例ではクラス Gut に関数 rwRestoreGuts を作成します。

void rwRestoreGuts(RWvistream& stream, const Gut& gut) {

  // 抽出演算子を使って、基本型オブジェクト、
  // Rogue Wave オブジェクトおよび RWCollectable から
  // 派生したオブジェクトを復元する

  stream
    >> gut.fundamentalType_
    >> gut.aRogueWaveObject_
    >> gut.anotherRogueWaveObject_
    >> gut.pointerToAnRWCollectable_;

  // RWCollectable 以外のオブジェクトを指す
  // ポインタを復元するにはコツがいる

  bool isValid;
  stream >> isValid;                   // これは nil ポインタか?

  if (isValid)                         // そうでない場合は
    stream >> gut.pointerToAnObject_;  // ポインタを復元する
  else                                 // そうである場合は、
    gut.pointerToAnObject_ = rwnil;    // ポインタを nil に設定する
}

void rwRestoreGuts(RWFile& stream, Gut& gut) {
  // この関数の本体は、rwRestoreGuts(RWvostream& stream, Gut& gut)
  // と同一である

14.4.5 ユーザが設計したクラスの同形永続性

14.3.1.2 節では、ポインタを含むコレクションに単純永続性を実装するコード例について説明しました。その例では、単純永続性は元のコレクションの参照関係を保持しないことを示しました。

次の例では、14.3.1.2 節で設定したコレクションに同形永続性を実装します。Team には、3 人の Developer が含まれています。図 7 は、オリジナルの Team コレクションの参照関係と、それを同形永続性で保存し復元したあとの Team コレクションの参照関係を示したものです。

図 7. 同形永続性

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

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

コードを見ると、他の Developer を指している Developer::alias_ メンバがどのように保存され復元されたかがわかります。Developer::name_ を保存したあと、Developer の関数 rwSaveGuts が、alias_ がメモリにある Developer を指しているかどうかを調べていることがわかります。Developer を指していない場合、rwSaveGuts は論理値 false を保存して alias_ が nil ポインタであることを示します。alias_ が Developer を指している場合、rwSaveGuts は論理値 true を保存します。この作業を行ったあと、最終的に rwSaveGuts は alias_ が指している Developer の値を格納します。

このコードは、新しい Developer と既存の Developer を区別することができます。

これは、RWDEFINE_PERSISTABLE(Developer) によって生成される挿入演算子がそれまでに格納された Developer を記録するからです。挿入演算子 operator<< によってストリームに Developer がまだ格納されていない場合にだけ、演算子 operator<< が rwSaveGuts を呼び出します。

Developer オブジェクトが復元されるとき、Developer の抽出演算子 operator>> が呼び出されます。挿入演算子と同様に、抽出演算子は 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>

//------------------ Declarations ---------------------

//------------------- 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 and 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){
// 以下によって呼び出される:
//    RWFile& operator<<(RWFile& strm, const Developer& item)

  file << developer.name_;        // name を保存

  // alias_ がメモリにある Developer を指しているかどうか調べる。
  // 指していない場合は、rwSaveGuts が論理値 false を格納して
  // alias_ が nil ポインタであることを示す。
  // 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_;        // name を保存

  // See if alias_ is pointing to a Developer in memory.

  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_;        // name を復元する。

  // developer.alias_ が Developer を指すべきか?

  RWBoolean alias;
  file >> alias;

  // alias_ が Developer を指すべきである場合は、
  // rwRestoreGuts が Developer オブジェクトを復元し、
// そして、rwRestoreGuts は復元された Developer を指すように alias_ を更
  // 新する。

  if (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_;     // 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.

注釈

  1. ^ C++ のテンプレート機構のために、これを行うことができません。ただし、ある位置に保存されているポインタは別の位置に注入されると、めんどうなことになるので、これは良い制約だと言えます。
  2. ^ テンプレートクラスの場合、あるコンパイラでは、ソースファイルが RWDECLARE_PERSISTABLE を使用したヘッダファイルと同じベース名でなければならないことがあります。