Tools.h++ 7.0 ユーザーズガイド: 14 - RWCollectable クラスの設計

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

# RWCollectable クラスの設計 | # 14 |

RWCollectable から派生するクラスには、2 つの重要な利点があります。これらのクラスは、やはり RWCollectable から派生した Smalltalk 式コレクションで使用可能であるということと、強力な多相持続性機構を使用できる唯一のコレクションクラスの集合であるということです。この節では、RWCollectable クラスのいくつかの例を示して、独自の RWCollectable クラスを作成する方法を説明します。

この節では、多相持続性機構自体については説明しません。多相、同形、単純の各持続性については前の章の「持続性」で詳しく説明してあります。要約すると、多相持続性とは、ストリームまたはファイルとの間で、持続オブジェクト間のポインタの関係が維持されるような方法でオブジェクトを格納して取り出し、それらのオブジェクトの型を知らなくても復元できる機能のことをいいます。

## RWCollection クラスを設計する理由

RWCollectable クラスの実際の設計方法を説明する前に、RWCollectable クラスを設計する理由について具体的な例を示します。

ユーザーがバス会社を経営していると想定します。乗務追跡システムの一部を自動化するために、バス、バスの顧客の集合、実際の乗客の集合を表わすクラスを作成する必要があります。ある人間が乗客になるためには、まず顧客にならなければなりません。したがって、顧客の集合は、乗客の集合の上位セットになります。また、1 人の人間は物理的に特定のバスに 1 回しか乗ることができないため、同じ人間を顧客リストに 2 回以上記載しても意味はありません。このシステムの開発者は、どちらのリストにも重複がないように注意する必要があります。

もし重複があると問題が起こる可能性があります。プログラムで、バスとその顧客に関する情報を保存して復元しなければならない場合を考えてください。バスを多相保存する必要があるとき、プログラムが顧客の集合に続いて、乗客の集合を単純に反復してそれぞれ 1 回ずつ保存すると、顧客でも乗客でもある人間が 2 回保存されてしまいます。プログラムがそのバスを多相復元した場合、乗客のリストはすでに顧客リスト内にある人間を単純には参照しません。それどころか、各乗客が両方のリストに別々のインスタンスを持つことになります。

ある人間がすでにストリームに多相保存されていて、その人間をもう一度保存するのではなく、前のインスタンスへの参照だけを保存すべきときを認識する方法が必要です。

これが、クラス RWCollectable の仕事です。RWCollectable から継承するオブジェクトは、その内容だけではなく、RWCollectable から継承する他のオブジェクトとの関係も保存することができます。この機能を同形持続性と呼びます。クラス RWCollectable には同形持続性がありますが、それだけではなく、保存または復元されるオブジェクトの型を実行時に判別することができます。RWCollectable によって提供される型の持続性を多相持続性と呼び、同形持続性の上位セットと考えます。

### RWCollectable クラスの例

次のコードは、前の節で説明したクラスをどのように宣言するかを示しています。その後で、マクロ `RWDECLARE_COLLECTABLE` を使用して、この機能を使う理由を説明します。この例の元となった完全なコードは、この例の章の最後に示してあります。この例のファイルは、`toolexam` ディレクトリの中に `Bus` という名前であります。

    class Bus : public RWCollectable  {
      RWDECLARE_COLLECTABLE(Bus)

    public:

       Bus();
       Bus(int busno, const RWCString& driver);
       ~Bus();

       // クラス 「RWCollectable」から継承
       Rwspace     binaryStoreSize() const;
       int         compareTo(const RWCollectable*) const;
       RWBoolean   isEqual(const RWCollectable*) const;
       unsigned    hash() const;
       void        restoreGuts(RWFile&);
       void        restoreGuts(RWvistream&);
       void        saveGuts(RWFile&) const;
       void        saveGuts(RWvostream&) const;

       void        addPassenger(const char* name);
       void        addCustomer(const char* name);
       size_t      customers() const;
       size_t      passengers() const;
       RWCString   driver() const    {return driver_;}
       int         number() const    {return busNumber_;}

    private:

       RWSet       customers_;
       RWSet*      passengers_;
       int         busNumber_;
       RWCString   driver_;
    };

    class Client : public RWCollectable  {
      RWDECLARE_COLLECTABLE(Client)
      Client(const char* name) : name_(name) {}
    private:
      RWCString    name_;
    // この例に関する他のクライアント情報を無視する
    };

両方のクラスが RWCollectable からどのように継承しているかに注意してください。項目の重複が認められないクラス RWSet を使用して、顧客の集合を実装することにしました。これは、同じ人間が顧客リストに 2 回以上記載されないよう保証します。同じ理由で、クラス RWSet を使用して、乗客の集合を実装することにしました。しかし、この集合をヒープ上でライブな状態にしました。これにより、次の説明でいくつかの点がわかりやすくなります。

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

次に、ユーザーのオブジェクトを RWCollectable から継承させる方法の概要を示します。

1. デフォルトコンストラクタを定義します。330 ページの「デフォルトコンストラクタの定義」を参照してください。

2. マクロ `RWDECLARE_COLLECTABLE` をユーザーのクラス宣言に追加します。331 ページの「RWDECLARE_COLLECTABLE() をユーザーのクラス宣言に追加する」を参照してください。

3. コンパイルするソースファイル (`.cpp`) の 1 つだけに `RWDEFINE_COLLECTABLE` か `RWDEFINE_NAMED_COLLECTABLE` のどちらかの定義マクロを追加することによって、ユーザーのクラスにクラス識別子を指定します。331 ページの「ユーザーのクラスにクラス識別子を指定する」を参照してください。

4. 継承された仮想関数の定義を必要に応じて追加します。継承された定義を使用することもできます。337 ページの「仮想関数の定義を追加する」では、次の仮想関数について説明しています。

       Int compareTo(const RWCollectable*) const;
       RWBoolean isEqual(const RWCollectable*) const;
       unsigned hash() const;

5. デストラクタを定義する必要があるかどうかを考えます。345 ページの「オブジェクトの破壊」を参照してください。

6. クラスに持続性を追加します。継承された定義を使用するか、または次の関数に定義を追加することができます。346 ページの「多相持続性の追加方法」を参照してください。

       RWspace binaryStoreSize() const;
       void restoreGuts(RWFile&);
       void restoreGuts(RWvistream&);
       void saveGuts(RWFile&) const;
       void saveGuts(RWvostream&) const;

これらの手順に続いて、RWFactory についての注意事項があります。358 ページの「RWFactory に関する注意」を参照してください。

### デフォルトコンストラクタの定義

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

デフォルトコンストラクタは C++ においてオブジェクトのベクトルを作成するために必要です。したがって、デフォルトコンストラクタはできるだけ指定するようにしてください。次に、例の Bus クラス用に考えられるデフォルトコンストラクタの定義を示します。

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

### RWDECLARE_COLLECTABLE() をユーザーのクラス宣言に追加する

326 ページの「RWCollectable クラスの例」に示した例には、Bus の宣言中にマクロ呼び出し `RWDECLARE_COLLECTABLE(Bus)` が含まれています。このマクロは、クラス名を引数として使用して、ユーザーのクラス宣言に挿入する必要があります。マクロを使用すると、必要なメンバー関数すべてが正しく宣言されます。

### ユーザーのクラスにクラス識別子を指定する

多相持続性を使用すると、あるクラスを 1 つの実行可能プログラムに保存して、それを異なる実行可能プログラム、あるいは、元の実行可能プログラムの再度の実行で復元することができます。実行可能プログラムでの復元では、そのクラスの型を前もって知らなくても、そのクラスを使用することができます。多相持続性を可能にするために、固有で1、変更されることのない識別子がクラスに必要です。RWCollectable から派生したクラスは多相持続性があるため、このような識別子が必要です。

識別子は数字か文字列のどちらかです。数値識別子は `RWClassID` という型名に typedef された `unsigned short` 型です。文字列識別子は `RWStringID` という型名に typedef された型です。数値識別子を指定する場合、ユーザーのクラスには文字列識別子が自動的に生成されます。これは、クラスの名前と同じ順序の文字シーケンスになります。同様に文字列識別子を指定する場合も、クラスには、実行可能コードでの使用時に、数値 ID が自動的に生成されます。

Tools.h++ には、ユーザーが設計したクラスに識別子を指定するための定義マクロが 2 つあります。数値 ID を指定したい場合は、次のマクロを使用してください。

      RWDEFINE_COLLECTABLE (className, numericID)

文字列 ID を指定したい場合は、次のマクロを使用してください。

      RWDEFINE_NAMED_COLLECTABLE (className, stringID)

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

      RWDEFINE_COLLECTABLE(Bus, 200)

または

      RWDEFINE_NAMED_COLLECTABLE(Client, "a client")

最初のマクロは、数値 ID「200」をクラス Bus に与え、2 番目のマクロは、文字列 ID「`a client`」をクラス Client に与えています。

このマニュアルの残りでは、RWDEFINITION_MACRO を使用して、どちらかのマクロも選択できることを示します。例のコードでは、どちらか一方のマクロをとります。

どちらのマクロも、仮想関数 `isA()` と `newSpecies()`2 用の定義を自動的に提供します。335 ページの「仮想関数 isA()」から 358 ページの「RWFactory に関する注意」まででは、これらの仮想関数について説明し、Tools.h++ のバージョン 7 の新機能である `stringID()` メソッドについて述べ、多相持続性の実装に役立つ RWFactory クラスの簡単な説明も示します。

#### 仮想関数 isA()

仮想関数 `isA()` はクラス識別子を返します。これは、オブジェクトのクラスを識別する固有の番号です。この識別子を使用すれば、オブジェクトが属するクラスを判別することができます。次に、マクロ `RWDECLARE_COLLECTABLE` によって提供される関数宣言を示します。

      virtual RWClassID isA() const;

`RWClassID` は、実際には `unsigned short` への typedef です。0x8000 (16 進) 以上の数字は、Rogue Wave による使用のために予約されています。0x0001 から 0x7fff までの数値クラスを選択することができます。Tools.h++ クラスライブラリの `<rw/tooldefs.h>` には、クラスシンボルの集合が定義されています。一般に、これらのシンボルは、二重下線に続けてすべて大文字のクラス名というパターンをとります。次に例を示します。

      RWCollectableString yogi;
      yogi.isA() == __RWCOLLECTABLESTRING;           // TRUE と評価

マクロ `RWDECLARE_COLLECTABLE(className)` は、自動的に `isA()` 用の宣言を提供します。どちらの RWDEFINITION_MACRO も定義を提供します。

#### 仮想関数 newSpecies()

この関数は、自分自身と同じ型の新規オブジェクトへのポインタを返します。次に、マクロ `RWDECLARE_COLLECTABLE` によって提供される関数宣言を示します。

        virtual RWCollectable*  newSpecies() const;

定義は、どちらかの RWDEFINITION_MACRO によっても自動的に提供されます。

#### 関数 stringID()

`stringID()` 関数の動作は仮想関数に似ていますが、仮想関数ではありません3。これは、オブジェクトのクラスを識別する固有の文字列である RWStringID のインスタンスを返します。RWStringID は、クラス RWCString から派生します。デフォルトでは、クラスの文字列識別子は、クラスの名前と同じです。RWStringID は、RWClassID のかわりに、あるいは、その補助として使用することができます。

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

クラス 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` の typedef、`Rwspace` は `unsigned long` の typedef、`RWClassID` は `unsigned short` の typedef です。クラス RWCollectable から派生するクラスはすべて、これらのメソッドをすべて理解できなければなりません。基底クラス RWCollectable では、これらすべてにデフォルトの定義が与えられますが、クラス設計者は、手元にあるクラスに合わせた定義を提供するようにしてください。

これらの仮想関数については、いくつかに分けて説明します。デストラクタについては 345 ページの「オブジェクトの破壊」、関数 `binaryStoreSize()`、`saveGuts()`、`restoreGuts()` については 346 ページの「多相持続性の追加方法」で説明します。ここでは、クラスに持続性を追加する方法を説明します。仮想関数 `isA()` と `newSpecies()` はマクロによって宣言され定義されるため、前出の「仮想関数 isA()」と「仮想関数 newSpecies()」で説明しました。この節では、残りの関数 `compareTo()`、`isEqual()`、`hash()` について説明します。344 ページの「compareTo()、isEqual()、hash() の例」には、これらのすべての関数が同じデータをどう処理するかを示す非常に簡単な例を示してあります。

#### 仮想関数 compareTo()

仮想関数 `compareTo()` は、オブジェクトを相対的に順序付けるために使用されます。この関数は、RWBinaryTree や RWBTree のように、このような順序付けに依存するコレクションクラスで必要です。次にその宣言を示します。

      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 に挿入する必要がある場合、このグループは、そのバス番号によってソートされます。他にも多数の選択肢があることに注意してください。たとえば、ドライバー名を使用して、ドライバー名によってソートさせることもできます。どの選択肢を使用するかは、問題によって異なります。

ここまでは、`c` が指す RWCollectable の実際の型を常に Bus と想定してきましたが、ここに危険があります。不注意なユーザーがたとえば、RWCollectableString をコレクションに挿入した場合、キャスト (`const Bus*`) `c` の結果は無効なので、その間接参照によって障害が起こるおそれがあります4。多重定義されたすべての仮想関数は、同じ識別記号を共有しなければならないため、最下位の共通部分 (各クラスが共通して継承している基底クラス。この場合はクラス RWCollectable) を返す必要があります。この結果、コンパイル時の型検査がほとんど失敗します。

> 注 - コレクションのメンバーが同種 (つまり、すべてが同じ型) であるか、またはそれらを区別する方法があるかに注意する必要があります。このためには、メンバー関数 `isA()` か `stringID()` を使用することができます。

#### 仮想関数 isEqual()

仮想関数 `isEqual()` は、192 ページの「テスト関数」で説明されている総称コレクションクラスのテスター関数に似た役割を果たします。

      RWBoolean  isEqual(const RWCollectable* c) const;

関数 `RWBoolean isEqual(const RWCollectable*)` は、オブジェクトとその引数が等値であると見なされる場合は `TRUE`、そうでない場合は `FALSE` を返します。等値性の定義はクラス設計者が行います。クラス RWCollectable に定義されたデフォルト定義では、2 つのアドレスの等値性、つまり同一性がテストされます。

`isEqual` を同値であると定義する必要はないことに注意してください。`isEqual` は、2 つのオブジェクトがある意味で等値であることを意味する場合があります。実際には、2 つのオブジェクトの型は同じでなくてもかまいません。唯一の要件は、引数として渡されるオブジェクトが型 RWCollectable を継承することだけです。ユーザーが、実行するすべての型キャストが適切であることを確認する必要があります。

また、等しいと比較される (つまり、`compareTo()` がゼロを返す) 2 つのオブジェクトが `isEqual()` から `TRUE` を返さなければならないという公式の要件はないことにも注意してください。ただし、`TRUE` を返さないような状況はほとんど想像できません。また、異なるハッシュ値を持つオブジェクトに対して `isEqual` テストが真を返すクラスを設計することもできます。こうすると、ハッシュにもとづくコレクションでこのようなオブジェクトを検索できなくなります。

Bus クラスに適切な `isEqual` の定義は次のようになります。

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

ここでは、バス番号が同じであれば等しいバスであると見なしています。別の選択をすることもできます。

#### 仮想関数 hash()

関数 `hash()` は、オブジェクトに適切なハッシュ値を返します。次に関数の宣言を示します。

      unsigned  hash() const;

クラス Bus の `hash()` 用に考えられる定義は次のようになります。

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

上記の例では、単純にバス番号をハッシュ値として返しています。かわりに、ドライバーの名前をハッシュ値として選択することもできます。

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

この例で、`driver_` はすでにハッシュ関数が定義された RWCString 型のメンバーです。

> 注 - `isEqual` で `TRUE` と見なされる 2 つのオブジェクトが同じ値にハッシュされるものと想定します。

#### compareTo()、isEqual()、hash() の例

`compareTo()`、`isEqual()`、`hash()` という 3 つの継承された仮想関数について説明してきました。次に、オブジェクトの集合を定義して、これらの関数を適用する例を示します。これらの関数の結果は、コード内のコメントに示します。

    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 を返す (オペレーティングシステムによって異なる)

RWCollectableStrings の `compareTo()` 関数が、文字列の大文字小文字を区別して字句どおりに比較するよう定義されていることに注意してください。詳細については、『Tools.h++ 7.0 クラスライブラリ・リファレンスマニュアル』のクラス RWCString を参照してください。

## オブジェクトの破壊

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

C++ のすべてのクラスと同様、`RWCollectable` から継承されるオブジェクトは、保持している資源を解放するためにデストラクタを必要とすることがあります。`Bus` の場合、乗客と顧客の名前はヒープから割り当てられた `RWCollectableString` であり、これらはヒープに返還する必要があります。これらの文字列はクラスの有効範囲外には決して現われないので、ユーザーによってアクセスされることはありません。したがって、デストラクタの中で確実に削除することができ、解放された領域を指すポインタが残ることはありません。

その上、`passengers_` が指している集合は `customers_` が指している集合に含まれているため `customers_` の内容だけを削除すれば十分であり、それ以外は何も削除する必要がありません。

次のように定義することができます。

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

ポインタ `passengers_` に関して `delete` を呼び出すことは、たとえそれが `NULL` ポインタであっても問題ないことを C++ 言語は保証しているので注意してください。

### 多相持続性の追加方法

仮想関数 `saveGuts()` と `restoreGuts()` は、RWCollectable オブジェクトの内部状態を保存して復元する義務があります。ユーザーの RWCollectable クラスに持続性を追加するには、オブジェクトのメンバーデータすべてを書き出すように、仮想メンバー関数 `saveGuts()` と `restoreGuts()` を上書きする必要があります。347 ページの「仮想関数 saveGuts(RWFile&) と saveGuts(RWvostream&)」と、352 ページの「仮想関数 restoreGuts(RWFile&) と restoreGuts(RWvistream&)」では、これらの関数を正しく定義するために使用できる方法を説明しています。353 ページの「多重参照されるオブジェクト」では、これらの関数が多重参照されるオブジェクトをどのように処理するかを示しています。

オブジェクトをファイルに多相保存するには、そのオブジェクトを格納するために割り当てる必要があるバイト数について若干の知識が必要です。`binaryStoreSize()` 関数はこの値を求めます。354 ページの「仮想関数 binaryStoreSize()」では、`binaryStoreSize()` を使用する方法を説明します。

RWCollection には、クラスから継承するコレクションを多相保存するために使用される独自のバージョンの `saveGuts()` と `restoreGuts()` 関数があります。356 ページの「カスタムコレクションの多相持続」では、これらの関数の動作について簡単に説明します。

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

仮想関数 `saveGuts(RWFile&)` と `saveGuts(RWvostream&)` は、クラス RWFile を使用してバイナリファイルに、あるいはクラス RWvostream を使用して仮想出力ストリームに RWCollectable オブジェクトの内部状態を多相保存する義務があります5。これにより、いつでも、どこでも、オブジェクトを復元することができます。次に、`saveGuts()` 関数を定義するためのいくつかの規則を示します。

1. ユーザーの基底クラスの状態を、そのバージョンの `saveGuts()` を呼び出すことによって保存します。

2. それぞれの型のメンバーデータについて、その状態を保存します。この方法は、そのメンバーデータの型によって異なります。

   - 基本型。基本型の場合は、データを直接保存します。RWFiles に保存する場合は `RWFile::Write()` を使用します。仮想ストリームに保存する場合は、挿入演算子 `RWvostream::operator<<()` を使用します。

   - Rogue Wave クラス。ほとんどの Rogue Wave クラスは、多重定義されたバージョンの挿入演算子を提供します。たとえば、RWCString は次のものを提供します。

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

     したがって、Rogue Wave クラスの多くは、簡単にストリームに挿入することができます。

   - RWCollectable から継承するオブジェクト。これらのオブジェクトのほとんどでは、次の大域関数を使用します。

         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_;              // NULL ポインタは自動的に検出される

    }

    void Bus::saveGuts(RWvostream& strm) const
    {  RWCollectable::saveGuts(strm); // 基底クラスを保存する
       strm << busNumber_;            // 基本型を直接書き込む
       strm << driver_ << customers_; // Rogue Wave 提供のバージョンを
                                      // 使用する
       strm << passengers_;           // NULL ポインタは自動的に検出される

    }

メンバーデータ `busNumber_` は `int` 型であり、C++ 基本型の 1 つです。これは、`RWFile::Write(int)`、または `RWvostream::operator<<(int)` のどちらかを使用して直接保存されます。

メンバーデータ `driver_` は RWCString です。これは、RWCollectable から継承されたものではありません。次のものを使用して保存されます。

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

メンバーデータ `customers_` は RWSet です。これは、RWCollectable から継承されたものです。次のものを使用して保存されます。

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

メンバーデータ `passengers_` には少し注意が必要です。このデータは、RWSet へのポインタであり、RWCollectable から継承します。しかし、このポインタは `NULL` である可能性があります。ポインタが `NULL` の場合、それを次のものに渡します。

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

これにより、`passengers_` を間接参照しなければならないため、障害が起こります。

      strm << *passengers_;

かわりに、例のクラスでは `passenger_` を RWSet* として宣言しているため、それを次のものに渡します。

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

これにより、`NULL` ポインタが自動的に検出され、そのレコードが格納されます。

#### 仮想関数 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_` が `NULL` ではない場合、この関数は、新しい RWSet をヒープから復元して、それへのポインタを返します。`NULL` の場合は、`NULL` ポインタを返します。どちらの場合も、`passengers_` の古い内容は置換されます。したがって、まず `delete passengers_` を呼び出す必要があります。

#### 多重参照されるオブジェクト

乗客名は、`customers_` によって指される集合と、`passengers_` によって指される集合の両方に存在することができます。つまり、どちらのコレクションにも同じ文字列が含まれます。Bus を復元する場合は、ポインタの関係が維持されているか、そして、復元によって文字列の別のコピーが作成されていないかを確認する必要があります。

幸い、ポインタの関係を保証するために特殊な処理を行う必要はありません。次の呼び出しについて考えてください。

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

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

`passenger_` は `customer_` の部分集合であるため、`addPassenger` は、顧客リストと乗客リストの両方に名前を挿入します。`aBus` を `aFile` に 保存する場合、両方のリストが 1 回の呼び出しで保存されます。まず顧客リストが保存され、次に乗客リストが保存されます。多相持続性機構は John への最初の参照を保存しますが、2 番目の参照の場合は、最初のコピーへの参照を格納するだけです。復元中、両方の参照は同じオブジェクトと解釈されて、コレクションの元の構造を複写します。

#### 仮想関数 binaryStoreSize()

`binaryStoreSize()` 仮想関数は、RWFile を使用してオブジェクトを格納するために必要なバイト数を計算します。この関数は次のとおりです。

        virtual Rwspace  binaryStoreSize() const;

この関数は、オブジェクトを格納する前に領域を割り当てる必要がある クラス RWFileManager と RWBTreeOnDisk に便利です。非仮想関数 `recursiveStoreSize()` は、実際に格納されたバイト数を返します。`recursiveStoreSize()` は、`binaryStoreSize()` を使用してこの処理を行います。

`binaryStoreSize()` のバージョンの作成は通常簡単です。メンバーデータを保存するかわりにそのサイズを追加する点を除いて、`saveGuts(RWFile&)` によって設定されたパターンに従うだけです。実際の違いは構文に関するものだけです。つまり、挿入演算子ではなく、`sizeof()` と次のメンバー関数を使用します。

- 基本型の場合は `sizeof()` を使用します。

- RWCollectable から継承するオブジェクトの場合は、ポインタが `NULL` でなければ、次のメンバー関数を使用します。

          RWspace RWCollectable::recursiveStoreSize();

- RWCollectable から継承するオブジェクトの場合は、ポインタが `NULL` であれば、次の静的メンバー関数を使用します。

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

#### カスタムコレクションの多相持続

Tools.h++ がクラス RWCollection に組み込んだ `saveGuts()` と `restoreGuts()` のバージョンは、ほとんどのコレクションクラスにとって十分なものです。関数 `RWCollection::saveGuts()` は、コレクション内の各項目に対して、次のものを繰り返し呼び出すことによって働きます。

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

同様に、`RWCollection::restoreGuts()` は、次のものを繰り返し呼び出すことによって働きます。

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

この演算子は、正しい型の新しいオブジェクトをヒープから割り当てて、`insert()` を呼び出します。Rogue Wave の Smalltalk 式コレクションクラスはすべて RWCollection から継承するため、必ずこの機構を使用します。

クラス RWCollection から継承する独自のコレクションクラスを作成する場合は、独自の `saveGuts()` か `restoreGuts()` を定義する必要はほとんどありません。

ただし例外があります。たとえば、クラス RWBinaryTree は独自のバージョンの `saveGuts()` を持ちます。これは、デフォルトバージョンの `saveGuts()` が項目を順番に保存するために必要です。バイナリツリーでは、これによって、ツリーが読み戻されたときに非常にバランスの悪いツリーが生じます。つまり、リンクリストの退化したケースです。したがって、`saveGuts()` の RWBinaryTree バージョンでは、ツリーをレベルごとに保存します。

クラスを設計する場合は、`saveGuts()` と `restoreGuts()` のカスタムバージョンを必要とする可能性がある、これに似た特殊要件があるかどうかを判断する必要があります。

### RWFactory に関する注意

RWDEFINITION_MACRO がどのように見えるかを検討します。

        RWDEFINE_COLLECTABLE(className, numericID)

または、文字列 ID を使用します。

        RWDEFINE_NAMED_COLLECTABLE(className, stringID)

バスの例の `.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++ 7.0 クラスライブラリ・リファレンスマニュアル』を参照してください。

## まとめ

一般に、自分でクラスを設計する場合、これらの仮想関数のすべてに関して必ずしも定義を与える必要はありません。たとえば、目的のクラスをソートされたコレクションに使用しないことがわかっている場合、`compareTo()` の定義は必要ありません。それでも、とにかく定義を与えておく方が得策です。それがコードの再利用を促進する最良の方法だからです。

次に、この章で例として取り上げたクラス `Bus` の完全なリストを示します。

    BUS.H:

    #ifndef __BUS_H__
    #define __BUS_H__

    #include <rw/rwset.h>
    #include <rw/collstr.h>

    class Bus : public RWCollectable  {
      RWDECLARE_COLLECTABLE(Bus)

    public:

      Bus();
      Bus(int busno, const RWCString& driver);
      ~Bus();

      // クラス "RWCollectable" からの継承:
      Rwspace      binaryStoreSize() const;
      int          compareTo(const RWCollectable*) const;
      RWBoolean    isEqual(const RWCollectable*) const;
      unsigned     hash() const;
      void         restoreGuts(RWFile&);
      void         restoreGuts(RWvistream&);
      void         saveGuts(RWFile&) const;
      void         saveGuts(RWvostream&) const;

      void         addPassenger(const char* name);
      void         addCustomer(const char* name);
      size_t       customers() const;
      size_t       passengers() const;
      RWCString    driver() const     {return driver_;}
      int          number() const     {return busNumber_;}

    private:

      RWSet        customers_;
      RWSet*       passengers_;
      int          busNumber_;
      RWCString    driver_;
    };

    class Client : public RWCollectable  {
      RWDECLARE_COLLECTABLE(Client)
      Client();
      Client(const char* name);
      Rwspace      binaryStoreSize() const;
      int          compareTo(const RWCollectable*) const;
      RWBoolean    isEqual(const RWCollectable*) const;
      unsigned     hash() const;
      void         restoreGuts(RWFile&);
      void         restoreGuts(RWvistream&);
      void         saveGuts(RWFile&) const;
      void         saveGuts(RWvostream&) const;
    private:
      RWCString    name_;
    //　この例では、他のクライアントの情報を無視する
    };
    #endif

    BUS.CPP:
    #include "bus.h"
    #include <rw/pstream.h>
    #include <rw/rwfile.h>
    #ifdef __GLOCK__
    #  include <fstream.hxx>
    #else
    #  include <fstream.h>
    #endif

    RWDEFINE_COLLECTABLE(Bus, 200)

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

    Bus::Bus(int busno, const RWCString& driver) :
      busNumber_  (busno),
      driver_     (driver),
      passengers_ (rwnil)
    {}

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

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

      if (passengers_)
        count += passengers_->recursiveStoreSize();

      return count;
    }

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

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

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

    size_t
    Bus::customers() const  {
      return customers_.entries();
    }

    size_t
    Bus::passengers() const    return passengers_ ? passengers_-
    >entries() : 0;
    }

    void
    Bus::saveGuts(RWFile& f) const  {
      RWCollectable::saveGuts(f);    // 基底クラスを保存する
      f.Write(busNumber_);           // 基本型を直接書き込む
      f << driver_ << customers_;    // RogueWave 提供版を使用する versions
      f << passengers_;        // NULL ポインタは自動的に検出される
    }

    void
    Bus::saveGuts(RWvostream& strm) const  {
      RWCollectable::saveGuts(strm);   // 基底クラスを保存する
      strm << busNumber_;              // 基本型を直接書き込む
      strm << driver_ << customers_;   // RogueWave 提供版を使用する
      strm << passengers_;     // NULL ポインタは自動的に検出される
    }

    void Bus::restoreGuts(RWFile& f)  {
      RWCollectable::restoreGuts(f);  // 基底クラスを復元する
      f.Read(busNumber_);             // 基本型を復元する
      f >> driver_ >> customers_;     // RogueWave 提供版を使用する

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

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

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

    void
    Bus::addPassenger(const char* name)  {
      Client* s = new Client(name);
      customers_.insert( s );

      if (!passengers_)
        passengers_ = new RWSet;

      passengers_->insert(s);
    }

    void
    Bus::addCustomer(const char* name)  {
      customers_.insert( new Client(name) );
    }

    ///////////////    ここに Client クラスのメソッドがある //////////////
    RWDEFINE_NAMED_COLLECTABLE(Client,"client")

    Client::Client() {} // RWCString デフォルトコンストラクタを使用する

    Client::Client(const char* name) : name_(name) {}

    RWspace
    Client::binaryStoreSize() const {
      return name_->binaryStoreSize();
    }

    int
    Client::compareTo(const RWCollectable* c) const  {
      return name_.compareTo(((Client*)c)->name_);
    }

    RWBoolean
    Client::isEqual(const RWCollectable* c) const  {
      return name_ == *(Client*)c;
    }

    unsigned
    Client::hash() const  {
      return name_.hash();
    }

    void
    Client::restoreGuts(RWFile& f)  {
      f >> name_;
    }

    void
    Client::restoreGuts(RWvistream& vis)  {
      vis >> name_;
    }

    void
    Client::saveGuts(RWFile& f) const  {
      f << name_;
    }

    void
    Client::saveGuts(RWvostream& vos) const  {
      vos << name_;
    }

    main()  {
      Bus theBus(1, "Kesey");
      theBus.addPassenger("Frank");
      theBus.addPassenger("Paula");
      theBus.addCustomer("Dan");
      theBus.addCustomer("Chris");

      { // ストリーム生存期間をブロックで制御する
        ofstream f("bus.str");
        RWpostream stream(f);
        stream << theBus;      // theBus を ASCII ストリームに存続させる
      }

      {
        ifstream f("bus.str");
        RWpistream stream(f);
        Bus* newBus;
        stream >> newBus;      // それを ASCII ストリームから復元する

        cout << "Bus number " << newBus->number()
             << " has been restored; its driver is "
             << newBus->driver() << ".\n";
        cout << "It has " << newBus->customers()
             << " customers and "
            << newBus->passengers() << " passengers.\n\n";

        delete newBus;
      }

      return 0;
    }

#### プログラム出力

    Bus number 1 has been restored; its driver is KeseyKesey.
    It has 4 customers and 2 passengers.

1 厳密には、指定された実行可能プログラムの中だけで、他の識別子と異なっていればかまいません。

2 RWDEFINITION_MACRO は、上記の 2 つのメソッドを実装する以上のことを行います。提供されたどちらのマクロも使用しないと決める前に、これらのマクロを詳しく検討して、マクロの機能をすべて理解したことを確認してください。

3 RWStringID の説明と、仮想関数を模倣する方法については、448 ページの「静的メソッドによる仮想メソッドの実装」を参照してください。このような方法でコードを作成することによって、前のバージョンの Tools.h++ からコンパイルされたオブジェクトコードとのリンクの互換性を維持します。

4 これは C++ の重大な欠点で、ユーザーは常に注意しなければなりません。特に、ユーザーが同種コレクションを持つことを予定している場合に注意が必要です。この問題については、「持続性」の「ソートされた RWCollections を使用して異種 RWCollectables を格納しない」という節を参照してください。

5 持続性機構の説明については、「持続性」の節を参照してください。