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

15.2 RWCollectable オブジェクトの作成方法

次に、RWCollectable から派生したオブジェクトの作り方の概要を示します。詳しい情報については、各ステップごとに示されている節を参照してください。

  1. デフォルトコンストラクタを定義する 15.2.1 節を参照。
  2. マクロ RWDECLARE_COLLECTABLE をクラス宣言に追加する 15.2.2 節を参照。
  3. 2 つの定義マクロ RWDEFINE_COLLECTABLE、RWDEFINE_NAMED_COLLECTABLE のいずれかを、コンパイルするソースファイル (.cpp) の 1 つだけに追加して、作成するクラスのクラス ID を指定する 15.2.3 節を参照。
  4. 必要に応じて、継承した仮想関数の定義を追加する。継承した定義を使うことができる場合があります。以下の仮想関数については、15.2.4 節で説明されています。
    Int         compareTo(const RWCollectable*) const;
    RWBoolean   isEqual(const RWCollectable*) const;
    unsigned    hash() const;
    
  5. デストラクタを定義する必要があるかどうかを考慮する 15.2.5 節を参照。
  6. クラスに永続性を追加する。継承した定義を使うことができる場合、あるいは以下の関数の定義を追加しなければならない場合があります 15.2.6 節を参照。
    RWspace     binaryStoreSize() const;
    void        restoreGuts(RWFile&);
    void        restoreGuts(RWvistream&);
    void        saveGuts(RWFile&) const;
    void        saveGuts(RWvostream&) const;
    

RWFactory について」で、これらの手順が説明されています 15.2.7 節を参照。

15.2.1 デフォルトコンストラクタを定義する

すべての RWCollectable クラスには、デフォルトコンストラクタが必要です。デフォルトコンストラクタは引数をとりません。永続性機構は、このコンストラクタを使って空のオブジェクトを作成し、次に適切な内容でそのオブジェクトを復元します。

デフォルトコンストラクタは、C++ でオブジェクトのベクトルを作成するために必要です。そのため、デフォルトコンストラクタを定義することは、よい習慣だと言えます。次に、作成するクラス Bus のデフォルトコンストラクタの可能な定義を示します。

Bus::Bus() :
  busNumber_  (0),
  driver_     ("Unknown"),
  passengers_ (rwnil)
{
}

15.2.2 RWDECLARE_COLLECTABLE() をクラス宣言に追加する

15.1.1節にある例には、Bus の宣言にマクロの呼び出し RWDECLARE_COLLECTABLE(Bus) が含まれています。このマクロは、クラス名を引数にしてクラス宣言に記述しなければなりません。このマクロを使用することにより、必要なメンバ関数すべてが正しく宣言されます。

15.2.3 作成するクラスのクラス ID を指定する

多形永続性を使うと、クラスを 1 つの実行可能ファイルに保存し、それを別々の実行可能ファイルに復元したり、あるいはオリジナルの実行可能ファイルを別に実行する時に復元することができます。実行可能ファイルの復元時にそのクラスを使うことができ、その時にはクラスの型が既知である必要はありません。多形永続性を与えるためには、クラスに唯一の 不変の識別子 (ID) が必要です。[1] RWCollectable から派生したクラスは、多形永続性を持つため、そのような識別子を必要とします。

識別子は、数字でも文字列でも構いません。数字の識別子は、RWClassID で typedef された unsigned short になります。文字列の識別子は、RWStringID で typedef されます。数字の識別子を指定する場合は、クラスは自動的に生成された文字列識別子を持ちます。これは、クラス名と同じ順序の文字列です。同様に、文字列の識別子を指定する場合、実行可能ファイルとして使用されるとき、クラスは自動的に生成された数字の識別子を持ちます。

Tools.h++ には、ユーザが作成するクラスに識別子を与えるマクロが 2 つ含まれています。数字の識別子を指定する場合は、次を使用してください。

RWDEFINE_COLLECTABLE (className, numericID)

文字列の識別子を指定する場合は、次を使用してください。

RWDEFINE_NAMED_COLLECTABLE (className, stringID)

定義マクロは、クラスのヘッダファイルに含めないことに注意してください。マクロは、そのクラスを使用する .cpp ファイルの一部になります。作成する RWCollectable クラスごとに、定義マクロを 1 つだけ、ソースファイル (.cpp) の 1 つにだけ含める必要があります。クラス名を最初の引数として、数字または文字列のクラス識別子を 2 番目の引数として使用します。バスの例では、次の定義マクロを含めることができます。

RWDEFINE_COLLECTABLE(Bus, 200)

あるいは:

RWDEFINE_NAMED_COLLECTABLE(Client, "a client")

最初の例では、クラス Bus に数値 ID 200 を、2 番目の例では、クラス Client に文字列 ID “a client” を与えます。

今後、このマニュアルでは RWDEFINITION_MACRO を使って、このマクロのどちらでも選択できることを表わします。コード例では、いずれかのマクロが選択されています。

どちらのマクロも自動的に仮想関数 isA() と newSpecies() を定義します。[2] 15.2.3.1 節から15.2.7 節では、これらの仮想関数について解説すると共に、バージョン 7 の Tools.h++ で追加された stringID() メソッドについて説明し、多形永続性の実装を助けるクラス RWFactory についても簡単に触れることにします。

15.2.3.1仮想関数 isA( )

仮想関数 isA() はクラス ID を返します: オブジェクトのクラスを識別する唯一の数字であるクラス ID を返します。これを使って、オブジェクトが属するクラスを判断することができます。マクロ RWDECLARE_COLLECTABLE は、次のように関数宣言します。

virtual RWClassID isA() const;

RWClassID は、実際には unsigned short として typedef されています。0×8000 (hex) 以上の数値は、Rogue Wave の予約値です。数値のクラス ID は、0×0001 から 0x7fff の間で選択できます。Tools.h++ クラスライブラリには、<rw/tooldefs.h> で定義されているクラスシンボルセットがあります。一般に、これらのクラスシンボルは、2 つの下線に続く大文字のクラス名から構成されています。例:

RWCollectableString yogi;
yogi.isA() == __RWCOLLECTABLESTRING;           // Evaluates TRUE

マクロ RWDECLARE_COLLECTABLE(className) で、自動的に isA() が宣言されます。そして、いずれかの RWDEFINITION_MACRO で、定義されます。

15.2.3.2 仮想関数 newSpecies()

この関数の役目は、自分自身と同じ型の新しいオブジェクトを指すポインタを返すことです。マクロ RWDECLARE_COLLECTABLE は、次のように関数宣言します。

virtual RWCollectable*  newSpecies() const;

この関数は、いずれのバージョンの RWDEFINITION_MACRO によっても自動的に定義されます。

15.2.3.3 関数 stringID()

関数 stringID() は仮想関数のように動作しますが、実際は仮想関数ではありません。[3] この関数は、RWStringID のインスタンス、つまりオブジェクトのクラスを識別する一意の文字列を返します。RWStringID はクラス RWCString から派生しています。デフォルトで、クラスの文字列識別子は、クラスと同じ名前になります。RWStringID は、RWClassID の代わりに使用したり、RWClassID を捕捉するために使うことができます。

15.2.4仮想関数の定義を追加する

クラス RWCollectable では、次の仮想関数を宣言します。

virtual                 ~RWCollectable();
virtual Rwspace         binaryStoreSize() const;
virtual int             compareTo(const RWCollectable*) const;
virtual unsigned        hash() const;
virtual RWClassID       isA() const;
virtual RWBoolean       isEqual(const RWCollectable*) const;
virtual RWCollectable*  newSpecies() const;
virtual void            restoreGuts(RWvistream&);
virtual void            restoreGuts(RWFile&);
virtual void            saveGuts(RWvostream&) const;
virtual void            saveGuts(RWFile&) const;

これらの関数では、RWBoolean は int として、RWspace は unsigned long として、RWClassID は unsigned short としてそれぞれ typedef されています。クラス RWCollectable から派生したクラスはどれも、これらのメソッドを理解できます。基底クラス RWCollectable でこれらすべてにデフォルト定義が与えられていますが、クラスの設計者としてユーザがカスタマイズした定義を与えるのが最善です。

これらの仮想関数については、2 つに分けて説明を行います。15.2.5 節 ではデストラクタについて、15.2.6 節 では、クラスに永続性を追加する方法について説明すると共に、binaryStoreSize()、saveGuts()、restoreGuts() の各関数について説明します。仮想関数 isA() と newSpecies() は、マクロによって宣言され、定義されます。これについては、15.2.3.1 節15.2.3.2 節ですでに説明してあります。この節では、compareTo()、isEqual()、および hash() について説明しました。同じデータをこれら 3 つの関数がどのように処理するかを示した簡単な例が、15.2.4.4 節にあります。

15.2.4.1 仮想関数 compareTo()

仮想関数 compareTo() は、互いの比較関係によってオブジェクトを順序付けるのに使用します。この関数は、 RWBinaryTreeRWBTree などのように、順序付けに依存しているコレクションクラスで必要になります。これは、以下のように宣言されます。

virtual int  compareTo(const RWCollectable*) const;

関数 int compareTo(const RWCollectable*) const は、引数よりもそれ自身が大きい場合は正の値を、引数よりもそれ自身が小さい場合は負の値を、引数とそれ自体が等しい場合は、ゼロを返すように定義してください。

あるオブジェクトが別のオブジェクトより、大きい、小さい、あるいは等しいことの定義と意味は、クラスの設計者に任されています。 RWCollectable クラスにみられるように、デフォルトの定義では 2 つのオブジェクトのアドレスを比較するようになっています。このデフォルトの定義はプレースホルダとして考えてください。実際には、デフォルト定義は、プログラムを実行する度に変化する可能性があるので、あまり意味のあるものではありません。

次に、compareTo() の定義例を示します。

int Bus::compareTo(const RWCollectable* c) const
{  const Bus* b = (const Bus*)c;
   if (busNumber_ == b->busNumber_) return 0;
   return busNumber_ > b->busNumber_ ? 1 : -1;
}

ここでは、バスの順序付けの方法として、バスの番号を使用しています。複数のバスを RWBinaryTree に挿入する場合、それらはバス番号によってソートされます。この他にもいろいろな定義があることに注意してください。例えば、運転手の名前を使うこともできます。この場合、バスは運転手の名前によってソートされます。どのような定義をするかは、特定の問題により異なります。

ただし、ここには落とし穴があります。 RWCollectable の引数 c が指している実際の型は、常に Bus であるという仮定に基づいているのです。ユーザがうっかりコレクションに RWCollectableString を挿入したりすると、キャスト (const Bus*)c は、無効なものになり、それを間接参照すると予期しない結果を招くことになります。[4] 多重定義された仮想関数は同じシグニチャを共有しなければならないため、この場合は、最小共有分母であるクラス RWCollectable にならざる得ないのです。つまり、コンパイル時の型検査はできません。

コレクションのメンバは同種 (すべて同じ型) であるか、あるいはそれらの型を区別する方法が存在しなければなりません。型の識別には、メンバ関数 isA() またはstringID() を使用できます。

15.2.4.2 仮想関数 Equal()

仮想関数 isEqual() は、12.3.1 節で説明した汎用コレクションのテスタ関数と同じような働きをします。

RWBoolean  isEqual(const RWCollectable* c) const;

関数 RWBoolean isEqual(const RWCollectable*) は、オブジェクトと引数が等値であるとみなされた場合は TRUE を、そうでなければ FALSE を返すように定義します。等値性の定義は、クラスの設計者に任されています。クラス RWCollectable で定義されているように、デフォルトの定義では 2 つのアドレスが等しいときに「等しい」とみなされます。すなわち、これはアイデンティティの同定と同じです。

2 つのオブジェクトが同一であることを「等しい」とみなすのであれば、isEqual を再定義する必要はありません。しかし、isEqual は、2 つのオブジェクトが別の条件で等しいことを意味することも可能です。また、2 つのオブジェクトは同じ型である必要がありません。唯一の条件は、引数として渡されるオブジェクトが型 RWCollectable を継承していることです。責任を持って、型キャストが適切に行われるようにしてください。

比較した結果等しい 2 つのオブジェクト (compareTo() がゼロを返す) で、isEqual() が TRUE を返さなければならないという規則はありません。ただし、そのような例はあまり存在しません。また、異なるハッシュ値を持つオブジェクトが、isEqual で TRUE を返すようなクラスを設計することも可能です。ただし、このようなオブジェクトは、ハッシュベースのコレクションで検索することが不可能になります。

Bus クラスの場合、isEqual の適切な定義は、以下のようになります。

RWBoolean Bus::isEqual(const RWCollectable* c) const
{  const Bus* b = (const Bus*)c;
   return busNumber_ == b->busNumber_;
}

ここでは、バス番号が同じ場合に、バスは等しいとみなされます。前にも述べたように、これ以外の定義も可能です。

15.2.4.3 仮想関数 hash()

関数 hash() は、オブジェクトに対して適切なハッシュ値を返すように定義します。ここでは、次のように宣言します。

unsigned  hash() const;

Bus クラスの hash() は、次のように定義することができます。

unsigned Bus::hash() const{
   return (unsigned)busNumber_;
}

上の例では、ハッシュ値として単純にバス番号を返します。以下のように、ハッシュ値としてドライバ名を使うこともできます。

unsigned Bus::hash() const{
   return driver_.hash();
}

上記例では、driver_ は、すでに定義されたハッシュ関数を持つ RWCString です。

アイデンティティ: 2 つのオブジェクトが isEqual で TRUE を返す場合は、ハッシュ値も同じになります。

15.2.4.4 compareTo()、isEqual()、および hash() の例

これまで、3 つの派生仮想関数、compareTo()、isEqual()、および hash() について説明しました。次に一組のオブジェクトを定義し、関数を適用する例を示します。関数の結果は、コードのコメントとして記載されています。

RWCollectableString a("a");
RWCollectableString b("b");
RWCollectableString a2("a");

a.compareTo(&b);      // -1 を返す
a.compareTo(&a2);     // 0 を返す (等しいとみなされる)
b.compareTo(&a);      // 1 を返す

a.isEqual(&a2);       // TRUE を返す
a.isEqual(&b);        // FALSE を返す

a.hash()              // 96 を返す (オペレーティングシステムにより異なる)

RWCollectableString の関数 compareTo() は、文字列を辞書的順序で大文字小文字の区別付きで比較するように定義されていることに注意してください。RWCString について詳しくは、『Tools.h++ Class Reference』を参照して ください。

15.2.5 オブジェクトの消滅

RWCollectable クラスを継承するオブジェクトはすべて仮想デストラクタを継承します。つまり、オブジェクトを削除するとき、そのオブジェクトの実際の型を知る必要がありません。これにより、コレクション内の項目はすべて、その実際の型が分からなくても削除されます。

C++ のクラスと同様に、RWCollectable を継承しているオブジェクトは、保持しているリソースを解放するためにデストラクタが必要です。 Bus の場合、乗客と利用者の名前は、ヒープ領域から割り当てられた RWCollectableString です。つまり、それらは返却しなければなりません。これらの文字列は、クラスの外からは見えないため、ユーザがそれらにアクセスすることを心配する必要はありません。つまり、これらの文字列は、ぶら下がりポインタが残る心配をせずに、デストラクタで削除できます。

さらに、passengers_ が指す群は customers_ が指す群に含まれているので、customers_ の内容だけを削除すればいいことになります。

デストラクタは次のように定義することができます。

Bus::~Bus()
{  customers_.clearAndDestroy();
   delete passengers_;
}

ポインタ passengers_ が nil ポインタであっても、delete を呼び出しても構わないことが、C++ 言語で保証されています。

15.2.6 多形永続性を追加する方法

仮想関数 saveGuts() と restoreGuts() には、RWCollectabl e オブジェクトの内部状態を保存および復元する責任があります。 RWCollectable クラスに永続性を追加するには、オブジェクトのメンバデータを書き出すように、仮想メンバ関数 saveGuts() と restoreGuts() をオーバーライドしなければなりません。15.2.6.1 節15.2.6.2 節では、これらの関数を正しく定義する方法を説明します。15.2.6.3 節では、これらの関数がどのように多重参照されているオブジェクトを処理するかを説明します。

オブジェクトを多形的にファイルに保存するためには、オブジェクトの格納に割り当てる必要があるバイト数を知らなければなりません。この値は、binaryStoreSize() 関数によって計算されます。binaryStoreSize() の使い方は、15.2.6.4 節を参照してください。

RWCollection には、そのクラスを継承するコレクションを多形的に保存するのに使用できる、独自のバージョンの saveGuts() と restoreGuts() 関数があります。これらの関数がどのように機能するかは、15.2.6.5節で簡単に説明されています。

15.2.6.1 仮想関数 saveGuts(RWFile&) と saveGuts(RWvostream&)

仮想関数 saveGuts(RWFile&) と saveGuts(RWvostream&) の役割は、RWCollectable オブジェクトの内部状態を、クラス RWFile を使ってバイナリファイルに、あるいはクラス Rwvostream を使って仮想出力ストリームに多形的に保存することです。これにより、その後、あるいは別の場所でオブジェクトを復元することができます。次に、saveGuts() 関数を定義するときの規則を示します。

  1. 基底クラスのバージョンの saveGuts() を呼び出すことによって基底クラスの状態を保存します。
  2. メンバデータの型ごとに、その状態を保存します。関数をどのように作成するかは、メンバデータの型によります。
    • プリミティブ- プリミティブの場合は、データを直接保存します。 RWFile に保存する場合は、RWFile::Write() を使います。仮想ストリームに保存する場合は、挿入演算子 RWvostream::operator<<() を使います。
    • Rogue Wave のクラス- ほとんどの Rogue Wave クラスには、多重定義されたバージョンの挿入演算子があります。例えば、RWCString の場合は以下のとおりです。
      RWvostream& operator<<(RWvostream&, const RWCString& str);
      

      つまり、Rogue Wave の多くのクラスでは、単にストリームに移動できます。

    • WCollectable を継承するオブジェクト- これらのオブジェクトのほとんどで、グローバル関数を使います。
      RWvostream& operator<<(RWvostream&,
                             const RWCollectable& obj);
      

      この関数は、オブジェクトに対して saveGuts() を再帰的に呼び出します。

これらの規則に留意した上で、例 Bus の関数 saveGuts() の定義をみてみましょう。

void Bus::saveGuts(RWFile& f) const
{  RWCollectable::saveGuts(f);    // 抽象基底クラスを保存する
   f.Write(busNumber_);           // プリミティブを直接書き込む
   f << driver_ << customers_;    // Rogue Wave が提供する
                                  //バージョンを使用する
   f << passengers_;              // nil ポインタを自動的に
                                  // 削除する
}

void Bus::saveGuts(RWvostream& strm) const
{  RWCollectable::saveGuts(strm); // 抽象基底クラスを保存する
   strm << busNumber_;            // プリミティブを直接書き込む
   strm << driver_ << customers_; // Rogue Wave が提供する
                                  // バージョンを使用する
   strm << passengers_;           // nil ポインタを自動的に
                                  // 削除する
}

メンバデータ busNumber_ は、整数型で C++ のプリミティブです。これは、RWFile::Write(int) または RWvostream::operator<< (int) のいずれかを用いて、直接格納されます。

メンバデータ driver_ は RWCString です。これは、RWCollectable を継承せず、次のようにして格納されます。

RWvostream& operator<<(RWvostream&, const RWCString&);

メンバデータ customers_ は RWSet です。これは、RWCollectable を継承せず、次のようにして格納されます。

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

最後のメンバデータ passengers_ の格納には、コツが要ります。このデータは、RWCollectable を継承する RWSet へのポインタです。ただし、ポインタが nil である可能性があります。ポインタが nil の場合は、それを次のように渡します。

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

passengers_ を間接参照しなければならないので、とんでもないことになるかもしれません。

strm << *passengers_;

その代わりに、passenger_ は RWSet * として宣言されているので、以下のように渡します。

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

これは、自動的に nil ポインタを検知して、その記録を格納します。

15.2.6.2 仮想関数 restoreGuts(RWFile&) と restoreGuts(RWvistream&)

saveGuts() と同様の方法で、これらの仮想関数は、ファイルまたはストリームから RWCollectable の内部状態を復元するために使用されます。Bus クラスのこれらの関数は、次のように定義されています。

void Bus::restoreGuts(RWFile& f)
{  RWCollectable::restoreGuts(f);   // 抽象基底クラスを復元する
   f.Read(busNumber_);              // プリミティブを復元する
   f >> driver_ >> customers_;      // Rogue Wave が提供する
                                    // バージョンを使う

   delete passengers_;              // 古い RWSet を削除する
   f >> passengers_;                // 新しいもので置き換える
}

void Bus::restoreGuts(RWvistream& strm)
{  RWCollectable::restoreGuts(strm);   // 抽象基底クラスを復元する
   strm >> busNumber_ >> driver_ >> customers_;

   delete passengers_;                 // 古い RWSet を削除する
   strm >> passengers_;                // 新しいもので置き換える
}

ポインタ passengers_ が以下のように復元されることに注意してください。

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

オリジナルの passengers_ が nil ポインタでない場合、この関数は新しい RWSet をヒープ領域から復元し、それを指すポインタを返します。それ以外の場合は、nil ポインタを返します。いずれの場合も、passengers_ の内容が置き換えられます。つまり、まず delete passengers_ を呼び出さなければならないということです。

15.2.6.3 多重参照されているオブジェクト

乗客名は、customers_ が指している群と passengers_ が指している群の両方に存在できます。つまり、両方のコレクションが同じ文字列を含んでいます。Bus を復元するとき、ポインタ関係が保持されること、および復元されたものが文字列の別のコピーを作成しないようにしなければなりません。

ポインタ関係がそのまま保持されるためには、何も特別な操作をする必要がありません。次の例について考えてみてください。

Bus aBus;
RWFile aFile("busdata.dat");

aBus.addPassenger("John");
aFile << aBus;

passenger_ は customer_ のサブセットであるため、関数 addPassenger は、利用者リストと乗客リストの両方に名前を載せます。aBus を aFile に保存するとき、両方のリストが 1 つの呼び出しで保存されます。まず、利用者リストが、次に乗客リストが保存されます。多形永続性の機構では John への最初の参照は保存されますが、2 回目の参照は、単に最初のコピーへの参照が格納されるだけです。格納の間、コレクションの元の参照関係を複写して、同じオブジェクトを指す両方の参照が解決されます。

15.2.6.4 仮想関数 binaryStoreSize()

仮想関数 binaryStoreSize() は、RWFile を用いてオブジェクトを格納するのに必要なバイト数を計算します。これは、以下のように宣言されます。

virtual Rwspace  binaryStoreSize() const;

この関数は、格納する前にオブジェクトに領域を割り当てる必要がある、クラス RWFileManagerRWBTreeOnDisk の場合に便利です。実際に格納されたバイト数は、非仮想関数 recursiveStoreSize() によって返されます。再帰的格納サイズには、binaryStoreSize() が使用されます.

独自の binaryStoreSize() を作成することは、通常難しいことではありません。saveGuts(RWFile&) によって設定されているパターンに従い、メンバデータを保存するかわりに、そのサイズを加算します。ただし、構文的な違いが 1 つあります。挿入演算子の代わりに、sizeof() と以下に示すメンバ関数を使います。

  • プリミティブの場合は、sizeof() を使います。
  • RWCollectable を継承するオブジェクトの場合、ポインタが nil でなければ、次のメンバ関数を使います。
    RWspace RWCollectable::recursiveStoreSize();
    
  • RWCollectable を継承するオブジェクトの場合、ポインタが nil であれば、次のメンバ関数を使います。
    RWspace RWCollectable::nilStoreSize();
    
  • その他のオブジェクトの場合は、メンバ関数 binaryStoreSize() を使います。

次に、クラス Bus の binaryStoreSize() 関数の定義例を示します。

RWspace Bus::binaryStoreSize() const{
   RWspace count = RWCollectable::binaryStoreSize() +
     customers_.recursiveStoreSize() +
     sizeof(busNumber_) +
     driver_.binaryStoreSize();

   if (passengers_)
     count += passengers_->recursiveStoreSize();
   else
     count += RWCollectable::nilStoreSize();

   return count;
}

15.2.6.5 カスタムコレクションに多形永続性を持たせる

ほとんどのコレクションクラスには、クラス RWCollection に組み込まれているバージョンの saveGuts() と restoreGuts() で十分です。関数 RWCollection::saveGuts() は、コレクションの各項目ごとに、以下を繰り返し呼び出すことによって機能します。

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

同様に、RWCollection::restoreGuts() は、以下を繰り返し呼び出すことによって機能します。

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

この演算子は、ヒープ領域を適切な型の新しいオブジェクトに割り当てて、insert() を呼び出します。Rogue Wave の Smalltalk_likeコレクションクラスは RWCollection を継承しているので、すべてこの機構を使用します。

RWCollection クラスを継承する独自のコレクションクラスを作成する場合でも、独自の saveGuts() や restoreGuts() を定義する必要はめったにありません。

ただし、これには例外があります。例えば、クラス RWBinaryTree は独自のバージョンの saveGuts() を持っています。デフォルトバージョンの saveGuts() は項目を順に格納するために、これが必要となります。バイナリツリーの場合、これは、ツリーを読み戻すときに極端にアンバランスなツリーになる結果を招きます (特に、リンクリストの場合)。そのため、RWBinaryTree のバージョンの saveGuts() は、ツリーのレベルごとに格納を行います。

独自のクラスを設計するときは、同様に saveGuts() と restoreGuts() をカスタマイズしなければならない特殊な必要性があるかどうか、検討しなければなりません。

15.2.7 RWFactory について

ここでは、RWDEFINITION_MACRO について検討します。

RWDEFINE_COLLECTABLE(className, numericID)

文字列 ID を使う場合は、

RWDEFINE_NAMED_COLLECTABLE(className, stringID)

bus の例の .cpp ファイルでは、マクロは以下のように使われています。

RWDEFINE_COLLECTABLE(Bus, 200)

そして

RWDEFINE_NAMED_COLLECTABLE(Client, "a client")

これらのマクロを使用すると、RWClassID だけを与えることによって、プログラムで独自のクラスの新しいインスタンスを作成できます。

Bus* newBus = (Bus*)theFactory->create(200);

または RWStringID:

Client* aClient = (Client*)theFactory->create("a client");

ポインタ theFactory は、クラス RWFactory の唯一のグローバルインスタンスを指すグローバルポインタです。これは、実行可能ファイルにインスタンスを持つ RWCollectable クラスすべての情報を保持します。RWFactory の create() メソッドは、実行時に型不明の永続性を持つオブジェクトに新しいインスタンスを作成するために、多形永続性機構によって内部的に使用されています。通常、独自のソースコードでこの機能を使うことはありません。というのは、RWFactory の使用は一般に、ユーザにとって明白だからです。RWFactory について詳しくは、『Tools.h++ Class Reference』を参照してください。

RWFactory は実際には特殊化された RWSet で、ハッシュ表を用いて実装されています。これは、RW_DEFAULT_CAPACITY バケット (通常 64) で作成されています。プログラムが RWCollectable から派生した別個のクラスを 64 個以上を使用する場合は、以下のようなコードを使ってバケット数を増加してください。

RWFactory *fac = getRWFactory();
fac->resize(resize-Factor * fac->buckets()); // 大きなサイズを
                                             // 選ぶ

この演算には、項目数に比例して時間がかかります。サイズを増加することが分かってい
る場合は、できるだけ早めに行うのが最も効果的です。

注釈

  1. ^ 識別子は、どの実行可能ファイルの識別子とも異なる必要があります。
  2. ^ RWDEFINITION_MACRO マクロは、この 2 つのメソッド以上のものを実装するので、 提供されているマクロを使用しないことにする場合も、マクロの詳細を検討し、何が行われているかをよく理解することが大切です。
  3. ^ RWStringIDと仮想関数をどのようにして模擬するかについては、 18.3.3.2 節を参照してください。前のバージョンの Tools.h++ でコンパイルされたオブジェクトコードのリンク互換性を保持するために、コードがこのように書かれています。
  4. ^ これは、ユーザが常に認識しなければならない C++ の欠陥です。ユーザが異種コレクションを使う場合には、特に注意が必要です。この問題について詳しくは、第 14 章「永続性」の「異種の RWCollectable を格納するために RWCollection をソートしないこと」を参照。
  5. ^ 永続性の機構については、第 14 章「永続性」を参照