Copyright 1999 Rogue Wave Software
Copyright 1999 Sun Microsystems, Inc.
Smalltalk 式コレクションクラス |
12 |
![]() |
これらのコレクションクラスには、2、3 の短所があります。つまり、動作がやや遅くなり、型保証が完全ではなく、オブジェクトが多少大きくなります。しかし、これらのクラスの能力とその明確なプログラミングインタフェースは、このような短所を補ってあまりあるものです。中でも、Smalltalk 式コレクションクラスは、異種コレクションと多相持続性に適しています。
Tools.h++ の Smalltalk 式クラスの多くには、対応する Smalltalk 名か総称名のどれかに対する typedef があります。この typedef は、プリプロセッサマクロ RW_STD_TYPEDEFS を定義することによって有効になります。typedef は自由に使用できますが、コードを保守しやすくするためには、実際のクラス名を使用するようお勧めします。
Smalltalk 式クラスの表
次の 2 つの表は、Tools.h++ Smalltalk 式クラスを要約したものです。表 12-1 は、17 のクラスすべてをその typedef、反復子、実装とともに示しています。表 12-2 は、クラス階層を図示しています。
クラス | 反復子 | "Smalltalk" typedef | 実装 |
---|---|---|---|
RWBag | RWBagIterator | Bag | 出現回数のディクショナリ |
RWBinaryTree | RWBinaryTree-Iterator | SortedCollection | B ツリー |
RWBTree | メモリー内の B ツリー | ||
RWBTreeDictionary | 連想 B ツリー | ||
RWCollection | RWIterator | Collection | 抽象基底クラス |
RWDlistCollectables | RWDlistCollec- tablesIterator | 二重リンクリスト | |
RWHashTable | RWHashTableIte-rator | ハッシュテーブル | |
RWHashDictionary | RWHashDiction-aryIterator | Dictionary | 連想ハッシュテーブル |
RWIdentityDictionary | RWHashDiction-aryIterator | IdentityDictionary | 連想ハッシュテーブル |
RWIdentitySet | RWSetIterator | IdentitySet | ハッシュテーブル |
RWOrdered | RWOrderedIterator | OrderedCollection | ポインタのベクトル |
RWSequenceable | RWIterator | Sequenceable | 抽象基底クラス |
RWSet | RWSetIterator | Set | ハッシュテーブル |
RWSlistCollectables | RWSlistCollectablesIterator | LinkedList | 一重リンクリスト |
RWSlistCollectablesQueue | なし | Queue | 一重リンクリスト |
RWSlistCollectablesStack | なし | Stack | 一重リンクリスト |
RWSortedVector | RWSortedVector- Iterator | 挿入ソートを使用したポインタのベクトル |
表 12-2 Smalltalk 式コレクションクラスのクラス階層
RWCollectable RWCollection (抽象基底クラス) RWBinaryTree RWBTree RWBTreeDictionary RWBag RWSequenceable (抽象基底クラス) RWDlistCollectables (二重リンクリスト) RWOrdered RWSortedVector RWSlistCollectables (一重リンクリスト) RWSlistCollectablesQueue RWSlistCollectablesStack RWHashTable RWSet RWIdentitySet RWHashDictionary RWIdentityDictionary
そこに挿入されるオブジェクトは、仮想関数 compareto() によって返される相対値にもとづいた順序で格納されます (337 ページの「仮想関数の定義を追加する」を参照してください)。次にコードを示します。
#define RW_STD_TYPEDEFS 1 //1 #include <rw/bintree.h> #include <rw/collstr.h> //2 #include <rw/rstream.h> main(){ // 空の SortedCollection を作成する SortedCollection sc; //3 // RWCollectableString をいくつか挿入する sc.insert(new RWCollectableString("George")); // 4 sc.insert(new RWCollectableString("Mary")); // 5 sc.insert(new RWCollectableString("Bill")); // 6 sc.insert(new RWCollectableString("Throkmorton")); // 7 // コレクションの中を反復する すべてのメンバーを出力する RWCollectableString* str; // 8 SortedCollectionIterator sci(sc); // 9 while( str = (RWCollectableString*)sci() ) // 10 cout << *str << endl; // 11 sc.clearAndDestroy(); return 0; } |
Bill George Mary Throkmorton |
//1 | プリプロセッサマクロ RW_STD_TYPEDEFS を定義することによって、Smalltalk 式 typedef のセットが使用可能になります。これによって、RWBinaryTree という名前の代わりに、typedef された "SortedCollection" という名前が使用できるようになります。この 2 つの名前の内容は同一です。 |
//2 | 2 番目の #include は、クラス RWCollectableString を宣言します。これは、基底クラス RWCString と RWCollectable から多重継承する派生クラスです。RWCollectableString は、RWCString から機能を継承し、クラス RWCollectable から「収集される能力」を継承します。 |
//3 | 空の SortedCollection がこの行で作成されています。 |
//4-//7 | 4 つの RWCollectableStrings がヒープから作成され、不特定の順序でコレクションに挿入されています。RWCollectableStrings のコンストラクタについての詳細は、『Tools.h++ 7.0 クラスライブラリ・リファレンスマニュアル』を参照してください。ここで割り当てられるオブジェクトは、通常、プログラムが終了する前に削除されるべきですが、例を明確にするためにこのステップは省略しました。 |
//8 | ここで RWCollectableString へのポインタが宣言および定義されています。 |
//9 | 反復子が SortedCollection sc から構築されています。 |
//10 |
この反復子は、コレクション全体を調べて各値を順序正しく読み取るのに使用されます。この反復子が "次の項目に進み、その項目へのポインタを返す" よう、関数呼び出し演算子 (すなわち operator()) が多重定義されています。Tools.h++ のすべての反復子はこのように機能します。反復子の例と解説については、このマニュアルの 123 ページの「コレクションクラスの反復子」と同様、Stroustrup (1986) も参照してください。次の型キャストが必要なのは、反復子が RWCollectable* (つまり RWCollectable へのポインタ) を返すためです。
これは実際のものへキャストしなければなりません。 |
//11 | 最後に、ポインタ str が逆参照されて出力されます。RWCollectableString の出力される機能は、基底クラス RWCString から継承されたものです。 |
このプログラムは実行時に 4 つの収集済み文字列を "順序正しく" 出力します。クラス RWCollectableString の場合、これは辞書式順序を意味します。
Smalltalk 式コレクションクラスの選択
前の節では、Smalltalk 式クラス、そのクラス階層、このクラスの動作例のリストを示しました。しかし、それらをどのように使用するかについては説明していません。この節では、各種の Smalltalk 式コレクションクラスについて概説します。実際の目的に適したものを選択する上での参考にしてください。選択に際しては、付録 A を参照することもできます。
バッグ、セット、ハッシュテーブル
クラス RWHashTable は最もわかりやすいクラスです。このクラスは、単純なハッシュ化ルックアップを使用し、特定のオブジェクトが格納されている "バケット" を見つけ、バケットの線形探索 (liner search) を行い、そのオブジェクトを見つけます。鍵となる概念は、同じ値を持つ (つまり、"isEqual" である) 複数のオブジェクトを 1 つのハッシュテーブルに挿入ができるということです。
ハッシュテーブルにもとづくこれらのクラスのいずれにおいても、オブジェクトの順序は意味を持たないことに注意してください。順序が重要である場合は、順序付きクラスを選択してください。
順序付きクラス
RWSequenceable から継承するクラスは固有の順序を持っています。つまり、"6 番目または i 番目のオブジェクト" とか、"最初" または "最後" のオブジェクト、といった表現に意味があるということです。
バイナリクラスおよび B ツリークラスはソートされているので、固有の順序を持つ、という意味で、"順序付きクラス" とみなすことができるでしょう。しかしその順序は挿入の順序ではなく、収集されたオブジェクトの相対的な値によって内部的に決定されます。つまりオブジェクトを、ソートされたコレクションの中の希望する位置に任意に挿入することはできないのです (ソートされた状態が失われてしまうという問題があるからです)。したがって、これらのクラスは別にサブクラス化されます。
辞書
辞書 ("マップ" と呼ばれることもある) は、外部キーを使用して値を見つけます。キーと値の型は同じでなくてもかまいません (違っているのが普通です)。辞書は、キーと値とを関連付けるものであると考えることができます。たとえば、ユーザーがコンパイラでシンボルテーブルを作成する場合、シンボル名をキーとして使用し、その再配置アドレスを値として使用するでしょう。これはセットを使用して作成する方法と対照的です。セットを使用する場合、名前とアドレスを 1 つのオブジェクトにカプセル化する必要があります。
RWCollection から継承される仮想関数
Smalltalk 式コレクションクラスの継承元は抽象基底クラス RWCollection であり、さらにその継承元は抽象基底クラス RWCollectable です (コレクションのコレクションを持つことが可能になります)。RWCollectable については 204 ページの「Smalltalk 式クラスの表」と第 14 章「RWCollectable クラスの設計」で説明します。
抽象基底クラスとは、それ自体が使用されるのではなく、他のクラスによって継承されることを目的としたクラスのことです。このクラスを一種の仮想クラスと考えると、仮想関数の意味を簡単に投影することができます。抽象規定クラスの仮想関数は、派生クラスの機能の青写真を提供します。RWCollection は、抽象基底クラスとして、insert()、remove()、entries() などの各種の仮想関数を宣言することにより、コレクションクラスに対して青写真を提供します。
insert()
仮想関数 insert() を使用すると、オブジェクトへのポインタをコレクションに挿入することができます。
virtual RWCollectable* insert(RWCollectable*);
この関数は、コレクションにとって最も自然な方法で挿入を行います。スタックの場合は、項目をスタックにプッシュします。キューの場合は、項目をキューに追加します。ソートされたコレクションの場合は、新しい項目の前にある項目が新しい項目よりも小さくなり、後ろにある項目が新しい項目よりも大きくなり、重複が可能な場合は、等しい項目は等しくなるように挿入されます。insert() を使用する例については、207 ページの「例」を参照してください。
必ず、実在するオブジェクトへのポインタを挿入してください。すべての RWCollection クラスは、find() などいくつかのメソッドで、その内容を間接参照する必要があるため、ゼロを挿入すると、このようなメソッドはクラッシュします。空のオブジェクトを格納しなければならない場合は、RWCollectable* などの適切な型のデフォルト作成オブジェクトを作成して挿入するようにしてください。RWCollection 中のすべてのオブジェクトを削除する予定がない場合は、大域 RWnilCollectable を選択することもできます。これは、キャッシュされた RWCollectable オブジェクトへのポインタです。ただし、RWnilCollectable は 1 つしかないため、もし削除すると、このクラスに対する他の参照すべてが無効なメモリーを参照することになるので注意してください。
find() とフレンド
次の仮想関数を使用すると、コレクションに含まれるオブジェクトの数と、特定のオブジェクトが含まれるかどうかをテストすることができます。
virtual RWBoolean contains(const RWCollectable*) const; virtual unsigned entries() const; virtual RWCollectable* find(const RWCollectable*) const; virtual RWBoolean isEmpty() const; virtual unsigned occurrencesOf(const RWCollectable*) const;
関数 contains() は、引数がコレクション内のある項目に等しい場合に TRUE を返します。「等しい」という意味は、テストの対象となるコレクションとオブジェクトの型によって異なります。コレクションのハッシュでは、まず引数をハッシュして、1 つのハッシュバケットの中にある候補者の数を減らしてから、仮想関数 isEqual() を使用して等値性をテストします (ここでは、相互に「isEqual (等しい)」である項目はすべて、同じ値にハッシュすることが重要です)。ソートされたコレクションは、引数に等しい項目、つまり compareTo() がゼロを返す項目を検索します。
仮想関数 occurrencesOf() は contains() と似ていますが、引数と等しい項目の数を返します。
仮想関数 find() は引数と等しい項目へのポインタを返します。
次の例は、207 ページの「例」にもとづくものですが、find() を使用してコレクション内の Mary の発生を検索し、occurencesOf を使用して Mary の発生回数を検索しています。
#define RW_STD_TYPEDEFS 1 #include <rw/bintree.h> //1 #include <rw/collstr.h> //2 #include <rw/rstream.h> main(){ // 空の SortedCollection を作成する SortedCollection sc; //3 // RWCollectableString をいくつか挿入する sc.insert(new RWCollectableString("George")); //4 sc.insert(new RWCollectableString("Mary")); //5 sc.insert(new RWCollectableString("Bill")); //6 sc.insert(new RWCollectableString("Throkmorton")); //7 sc.insert(new RWCollectableString("Mary")); //8 cout << sc.entries() << "\n"; //9 RWCollectableString dummy("Mary"); //10 RWCollectable* t = sc.find( &dummy ); //11 if(t){ //12 if(t->isA() == dummy.isA()) //13 cout << *(RWCollectableString*)t << "\n"; //14 } else cout << "Object not found.\n"; //15 cout << sc.occurrencesOf(&dummy) << "\n"; //16 sc.clearAndDestroy(); return 0; } |
5 Mary 2 |
//1-//7 | これらの行は 207 ページの「例」にあるものと同じです。 |
//8 | 値 "Mary" を持つインスタンスをもう 1 つ挿入します。 |
//9 | この文はソートされたコレクション内のエントリの総数 (5) を出力します。 |
//10 | 使い捨ての変数 "dummy" が作成されます。これは、"Mary" が含まれている文字列の出現を調べるために使用されます。 |
//11 | コレクションは、引数に一致するものとして最初に見つかったオブジェクトへのポインタを返すよう求められます。そのようなオブジェクトがなければ、ゼロポインタが返されます。 |
//12 | ポインタに対しゼロでないことを確かめます。 |
//13 | 念入りな検査。この例では、コレクション内の項目の型が RWCollectableString でなければならないことは明らかです。一般には、明らかであるとはいえません。 |
//14 | 行 13 の結果により、RWCollectableString ポインタへのキャストは安全です。このポインタは次に逆参照および出力されます。 |
//15 | ポインタ t がゼロであった場合、ここでエラーメッセージが表示されることになります。 |
//16 | occurrencesOf() への呼び出しは、引数と一致する項目の数を返します。この例では、2 つの項目が見つかりました ("Mary" が 2 回出現)。 |
remove() 関数
特定の項目を検索して削除するには、関数 remove() と removeAndDestroy() を使用することができます。
virtual RWCollectable* remove(const RWCollectable*); virtual void removeAndDestroy(const RWCollectable*);
関数 removeAndDestroy() は remove() と似ていますが、違うところは項目を返さずにすべての RWCollectable 項目に継承される仮想デストラクタを使用してそれを削除することです。この関数を使用するときは、項目は実際にヒープ (スタックではなく) から割り当てられており、他のコレクションと共有されていないということに注意しなければなりません。また、RWnilCollectable は キャッシュおいて RWCollectable を参照するので、削除しないように注意してください。
RWCollectable* oust = sc.remove(&dummy); //17 delete oust; //18 sc.removeAndDestroy(&dummy); //19
//17 | "Mary" が含まれている文字列のうち最初に出現したものを取り除き、そのポインタを返します。このポインタは、そのような項目がなければゼロになります。 |
//18 | 初めにヒープから割り当てられた項目を削除します。ポインタが NULL かどうかを調べる必要はありません。NULL ポインタを削除しても常に問題は生じないと言語が保証しているからです。 |
//19 | この文で、2 番目以降の "Mary" が取り除かれ、かつ削除されます。 |
apply() 関数
Smalltalk 式コレクションのメンバーを効率的に調べるには、メンバー関数 apply() を次のように使用します。
virtual void apply(RWapplyCollectable ap, void* x);
typedef void (*RWapplyCollectable)(RWCollectable*, void*);
void yourApplyFunction(RWCollectable* item, void* x)
Smalltalk 式コレクションの apply() 関数が総称コレクションの apply() 関数と似ていることに注意してください (200 ページの「適用関数」と比較してください)。相違点は、ユーザー定義関数の最初の引数の型にあります。Smalltalk 式コレクションは RWCollectable* を使用するのに対して、総称コレクションは type* を使用します。いずれのコレクションでも、ポインタ item を正しい派生クラスにキャストするように注意する必要があります。
適用関数は一般に、コレクションの全メンバーを調べるために "最も効率の良い方法" を採用します。これは適用関数の最大の長所です。その短所は、若干使いにくいところがあり、ユーザーが別の関数を用意しなければならないということです 1 。
関数 clear() および clearAndDestroy()
コレクションからすべての項目を削除するには、関数 clear() と clearAndDestroy() を使用することができます。
virtual voidclear(); virtual voidclearAndDestroy();
クラス変換
次の関数を使用すると、任意のコレクションクラスを RWBag、RWSet、RWOrdered、SortedCollection (つまり、RWBinaryTree) に変換することができます。
RWBag asBag() const; RWSet asSet() const; RWOrdered asOrderedCollection() const; RWBinaryTree asSortedCollection() const
他のコレクションの挿入と除去
これらの関数を使用すると、その引数の内容をそれぞれ挿入したり、削除することができます。
void operator+=(const RWCollection&); void operator-=(const RWCollection&);
typedef RWBoolean (*RWtestCollectable)(const RWCollectable*, const void*); RWCollection* select(RWtestCollectable tst, void*);
RWSequenceable から継承される仮想関数
RWCollectable から継承する抽象基底クラス RWSequenceable から継承するコレクションには、固有の順序があります。この節では、この順序を使用する、RWSequenceable から継承した仮想関数について説明します。たとえば、次の仮想関数を使用すると、コレクション内の i 番目の項目にアクセスすることができます。
virtual RWCollectable*& at(size_t i); virtual const RWCollectable* at(size_t i) const;
RWOrdered od; od.insert(new RWCollectableInt(0)); // 0 od.insert(new RWCollectableInt(1)); // 0 1 od.insert(new RWCollectableInt(2)); // 0 1 2 delete od(1); // RWOrdered に有効な可変要素を使用する od.at(1) = new RWCollectableInt(3); // 0 3 2
次の仮想関数はそれぞれ、コレクション内の最初の項目か最後の項目を返します。ただし、コレクションが空であれば NULL を返します。
virtual RWCollectable* first() const; virtual RWCollectable* last() const;
virtual size_t index(const RWCollectable*) const;
RWOrdered od; od.insert(new RWCollectableInt(6)); // 6 od.insert(new RWCollectableInt(2)); // 6 2 od.insert(new RWCollectableInt(4)); // 6 2 4 RWCollectableInt dummy(2); size_t inx = od.index(&dummy); if (inx == RW_NPOS) cout << "Not found.\n"; else cout << "Found at index " << inx << endl; |
Found at index 1 |
virtual RWCollectable* insertAt(size_t i, RWCollectable* c);
RWOrdered od; od.insert(new RWCollectableInt(6)); // 6 od.insert(new RWCollectableInt(2)); // 6 2 od.insertAt(1, new RWCollectableInt(4)); // 6 4 2
次のコードはこのことを説明しています。
SortedCollection sc; RWCollectableString member; sc.insert(&member); RWCollectableString target; RWCollectableString* p = (RWCollectableString*)sc.find(&target);
member.compareTo(&target); // これが呼び出させる target.compareTo(&member); // こちらではない
一般に、ハッシュと線形探索のこの組み合わせと、ほとんどのハッシュアルゴリズムの複雑さにより、ハッシュコレクション内でのオブジェクトの順序はあまり意味を持ちません。したがって、apply() 関数または反復子がハッシュテーブルを走査するとき、オブジェクトが何か特定の順序で調べられるということはありません。