Tools.h++ 7.0 ユーザーズガイド: 11 - 総称コレクションクラス

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

# 総称コレクションクラス | # 11 |

総称コレクションクラスとは、Tools.h++ に含まれるコレクションクラスの 2 番目に重要なカテゴリです。「総称 (generic)」と呼ばれる理由は、このコレクションクラスが `<generic.h>` の中で定義されているマクロを使用するからです。`<generic.h>` は、パラメータ化された型への初期の実装の 1 つです (『プログラミング言語 C++』を参考)。総称コレクションクラスは、真のテンプレート1よりも扱いにくいのですが、どのような C++ コンパイラにも移植できます。古いコンパイラでも使用できます。

ほとんどの総称コレクションクラスは、参照にもとづく意味解釈を使用します。つまり、110 ページの「コレクションクラスの格納方法」で説明したように、他のオブジェクトへのポインタを格納したり、検索したりします。これらのクラスでは、Rogue Wave コレクションクラスと同様、ユーザーがオブジェクトそのものの割り当てと開放を行う必要があります。

RWGVector(val)、RWGOrderedVector(val)、RWGSortedVector(val) の 3 つのベクトルにもとづく総称コレクションクラスは、値にもとづく意味解釈を使用します。これらのクラスは、型そのもの (オブジェクトへのポインタにもなる) を格納します。

記憶および検索の方法と基準はクラスごとに異なります。

## 例

この例は、RWGStack (総称スタック) を使用して `int` 型へのポインタのセットを後入れ先出し (LIFO) 方式でスタックに格納するというものです。この節では、コードを示した後でこのプログラムを 1 行ずつ検討しながら何が起こるかを説明します。

    #include <rw/gstack.h>                                     //1
    #include <rw/rstream.h>                                    //2

    declare(RWGStack, int)                                     //3

    main(){
       RWGStack(int) gs;                                       //4
       gs.push(new int(1));                                    //5
       gs.push(new int(2));                                    //6
       gs.push(new int(3));                                    //7
       gs.push(new int(4));                                    //8

       cout << "Stack now has " << gs.entries()
            << " entries\n";                                   //9

       int* ip;                                                //10
       while( ip = gs.pop() )                                  //11
       {
         cout << *ip << "\n";                                  //12
         delete ip;
       }
       return 0;
    }

#### プログラム出力

    Stack now has 4 entries
    4
    3
    2
    1

次に、このプログラムを 1 行ずつ説明します。

`//1` | この `#include` はプリプロセッサマクロ `RWGStackdeclare`(type) を定義します。このマクロは行数が多くて整然としていませんが、型 `type` のオブジェクトの総称スタックがどのように動作すべきかを記述しています。一般にこのマクロは、基盤にある実装 (一重リンクリスト (クラス RWSlist)) に対する制限付きのインタフェースとして機能します。制限されている点は、RWSlist の一部のメンバー関数 (スタックに関係するもの) しか使用できないということと、スタックに挿入する項目が `type` 型でなければならないということです。
`//2` | `<rw/rstream.h>` は Rogue Wave が提供する特殊なヘッダファイルです。このヘッダファイルには、コンパイラに応じて適切な接尾辞の付いた `<iostream.h>` が入っています。
`//3` | この行は、コンパイラ付属のヘッダファイル `<generic.h>` 内で定義されているマクロ `declare` を呼び出します。`declare(Class,type)` と引数を指定して呼び出した場合、引数 `type` を持つマクロ `Classdeclare` が呼び出されます。したがってこの場合は、引数 `int` を持つマクロ `RWGStackdeclare` が呼び出されます (`<rw/gstack.h>` で定義されています)。 つまり、`declare(RWGStack, int)` マクロを呼び出した結果、特にユーザーのプログラムに対して新しいクラスが作成されたということです。実際的には、この名前は RWGStack(int) で、`int` へのポインタのスタックです。
`//4` | この行で新しいクラス RWGStack(int) のインスタンス `gs` が作成されます。
`//5-//8` | ヒープから 4 つの `int` が作成され、スタックに挿入されました。行 8 が実行されると、`int 4` へのポインタがスタックの最上部にきて、`int 1` へのポインタが最下部にきます。
`//9` | スタック上に項目がいくつあるかを確認するため、クラス RWGStack(int) のメンバー関数 `entries()` が呼び出されます。
`//10` | `int` へのポインタが宣言および定義されます。
`//11` | スタックが空になるまでポップされます。メンバー関数 `pop()` がスタックの最上部にある項目へのポインタを返して取り除きます。スタック上にそれ以上項目がなければゼロを返し、それによって `while` ループが終了します。
`//12` | 各項目が逆参照および出力されます。

## 総称コレクションクラスの宣言

Tools.h++ のすべての総称コレクションクラスは、前述の例と同じような方法で ヘッダファイル `<generic.h>` 内で定義されている `declare` マクロを使用して宣言されます。しかし、Tools.h++ のクラスの宣言方法と、Stroustrup (1986) が示した方法との間には大きな違いが 1 つあります。これを要約すると次のようになります。

      typedef int* intP;
      declare(RWGStack, intP)                               //誤り
      declare(RWGStack, int)                                //正しい

Stroustrup 氏によると、クラスは収集された項目へのポインタに対して `typedef` を使用して宣言されます。Rogue Wave の総称クラスは、すべて項目名そのものを使用して宣言されます。これは参照にもとづくクラスと値にもとづくクラスの両方についていえることです。

## ユーザー定義関数

総称コレクションクラスのメンバー関数の中には、ユーザー定義関数へのポインタを必要とするものがあります。このようなユーザー定義関数にはテスト関数と適用関数の 2 種類があります。これらをこの節で説明します。

### テスト関数

この関数は次のような形式をとります。

      RWBoolean tester(const type* ty, const void* a)

ここで、tester は関数の名前、type は当該コレクションクラスのメンバーの型、`RWBoolean` は `int` に対する `typedef` です (とり得る値は TRUE か FALSE に限られます)。テスト関数の役目は、そのコレクションのあるメンバーが識別された場合にそれを知らせることです。これがどのように行われるか、あるいはオブジェクトを "識別" したとはどういう意味なのかを決定するのはユーザーです。ユーザーはアドレスを比較したり (2 つのオブジェクトが "同一" であるか調べる)、あるいはオブジェクトの中で一定の値を探したりする ("`isEqual`" か調べる) ことができます。最初の変数 `ty` はコレクションのメンバーを指定し、1 つの "候補" と考えることができます。"クライアントデータ" として考えることができる 2 番目の変数 `a` はユーザーが `ty` との一致をテストするために使用できます。

次の例は前述の例を拡張したものですが、`isEqual` のテストが問題になっています。スタックにいくつかの値をプッシュして、特定の値がスタックに存在するかどうかを確認しています。クラス RWGStack(type) のメンバー関数 `contains()` には、次のプロトタイプがあります。

      RWBoolean contains(RWBoolean (*t)(const type*, const void*),
                         const void* a) const;

最初の引数は `RWBoolean (*t)(const` type*, `const void*)` です。これはテスト関数へのポインタで、これに対して我々は適切な定義を与えなければなりません。

    #include <rw/gstack.h>
    #include <rw/rstream.h>

    declare(RWGStack, int)

    RWBoolean myTesterFunction(const int* jp, const void* a)     //1
    {  return *jp == *(const int*)a;                             //2
    }

    main(){
       RWGStack(int) gs;                                         //3
       gs.push(new int(1));                                      //4
       gs.push(new int(2));                                      //5
       gs.push(new int(3));                                      //6
       gs.push(new int(4));                                      //7

       int aValue = 2;                                           //8

       if ( gs.contains(myTesterFunction, &aValue) )             //9
         cout << "Yup.\n";
       else
         cout << "Nope.\n";

    while(!gs.isEmpty())
       delete gs.pop();

    return 0;
    }

#### プログラム出力

    Yup.

次に、このプログラムを 1 行ずつ説明します。

//1 | これがテスト関数です。最初の引数はコレクション内のオブジェクトの型へのポインタです (この場合は `int`)。2 番目の引数は任意の型のオブジェクトを指します。この例では、`int` も指します。どちらの引数も `const` ポインタとして宣言されています。一般にテスト関数は指しているオブジェクトの値を変更してはなりません。
//2 | 2 番目の引数が `const void*` から `const int*` に変換および逆参照されます。結果は `const int` になります。これは逆参照された最初の引数 (これも `const int`) と比較されます。最終的な結果は、2 つの `int` が同じ値を持つ (すなわち、両者が等しい) とき、このテスト関数は一致が起きたとみなします。特定の `int` を調べる (すなわち、同一性を調査する) ということもできました。
//3-7 | これらの行は、186 ページの「例」と同じです。`int` (へのポインタ) の総称スタックが宣言および定義されて 4 つの値がプッシュされます。
//8 | これがスタック内から探す値 (すなわち "2") です。
//9 | ここでメンバー関数 `contains()` が、テスト関数を使用して呼び出されます。contains() の 2 番目の引数 (変数 `aValue` へのポインタ) は、テスト関数の 2 番目の引数として現れます。関数 `contains()` はスタック全体を調査し、各項目ごとにテスト関数を呼び出し、テスト関数から一致の知らせがあるまで待ちます。一致の知らせがあれば、`contains()` は `TRUE` を返し、そうでなければ `FALSE` を返します。

テスト関数の 2 番目の引数は、必ずしもコレクションのメンバーと同じ型である必要はありません (上記の例ではそうなっていますが)。次は両者が同じ型でない場合の例です。

    #include <rw/gstack.h>
    #include <rw/rstream.h>

    class Foo {
    public:
      int data;
      Foo(int i) {data = i;}
    };

    declare(RWGStack, Foo)  // Foo へのポインタのスタック

    RWBoolean anotherTesterFunction(const Foo* fp, const void* a)
    {  return fp->data == *(const int*)a;
    }

    main(){
       RWGStack(Foo) gs;
       gs.push(new Foo(1));
       gs.push(new Foo(2));
       gs.push(new Foo(3));
       gs.push(new Foo(4));

       int aValue = 2;
       if ( gs.contains(anotherTesterFunction, &aValue) )
         cout << "Yup.\n";
       else
         cout << "Nope.\n";

    while(!gs.isEmpty())
         delete gs.pop();
       return 0;
    }

ここでは、`Foo` (へのポインタ) のスタックが宣言および使用されていますが、テスト関数へ 2 番目の引数として渡される変数の型はやはり `const int*` です。テスト関数はこれを考慮に入れなければなりません。

### 適用関数

この関数の一般形式は次のとおりです。

      void yourApplyFunction(type* ty, void* a)

ここで、`yourApplyFunction` は関数の名前、`type` は当該コレクションクラスのメンバーの型です。適用関数は、ユーザーに対してコレクションの各メンバーに何らかの演算を実行する (たとえば、出力したり画面上に描画したりする) 機会を与えます。2 番目の引数は、関数が使用する "ユーザー定義データ" (たとえば、オブジェクトを描画するウィンドウのハンドル) を保持するように設計されています。

次の例では、適用関数 `printAFoo` を使用して、総称二重リンクリストの RWGDlist(type) の各メンバーの値を出力しています。

    #include <rw/gdlist.h>
    #include <rw/rstream.h>

    class Foo {
    public:
      int val;
      Foo(int i) {val = i;}
    };

    declare(RWGDlist, Foo)

    void printAFoo(Foo* ty, void* sp){
       ostream* s = (ostream*)sp;
       (*s) << ty->val << "\n";
    }

    main(){
       RWGDlist(Foo) gd;
       gd.append(new Foo(1));
       gd.append(new Foo(2));
       gd.append(new Foo(3));
       gd.append(new Foo(4));
       gd.apply(printAFoo, &cout);

       while(!gd.isEmpty())
          delete gd.get();
       return 0;
    }

#### プログラム出力

    1
    2
    3
    4

これらの項目はリストの末尾に追加されます。各項目ごとに、`apply()` 関数はユーザー定義関数 `printAFoo()` を呼び出します。その際、最初の引数として項目のアドレスを、2 番目の引数として `ostream` のアドレスを渡します。`printAFoo()` の役目は、メンバーデータ `val` の値を出力することです。`apply()` はリストを始めから終わりまで走査するので、項目は挿入されたときと同じ順序で出力されます。『Tools.h++ 7.0 クラスライブラリ・リファレンスマニュアル』の「RWGDlist(type)」を参照してください。

ある程度注意すれば、適用関数はコレクション内のオブジェクトを変更するのに使用することができます。たとえば上記の例のメンバーデータ `val` の値を変更したり、すべてのメンバーオブジェクトを削除するのに使用することもできます。しかし後者については、そのコレクションを再度使用しないように注意しなければなりません。

1 総称クラスは、使用が簡単ですが作成は困難なものです。また、総称クラスは前処理マクロであり、ほとんどのデバッガはマクロコードを入力することができないため、デバッグも困難です。