Previous Next Contents Index


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

Smalltalk 式コレクションクラス

12


Smalltalk 式コレクションクラスは、Tools.h++ で提供される 3 番目の一般的なコレクションクラスです。言語 Smalltalk-80 にもとづいて、このコレクションクラスを使用する場合は、収集すべきオブジェクトは、抽象基底クラス RWCollectable から単一継承または多重継承しなければなりません。

これらのコレクションクラスには、2、3 の短所があります。つまり、動作がやや遅くなり、型保証が完全ではなく、オブジェクトが多少大きくなります。しかし、これらのクラスの能力とその明確なプログラミングインタフェースは、このような短所を補ってあまりあるものです。中でも、Smalltalk 式コレクションクラスは、異種コレクションと多相持続性に適しています。

Tools.h++ の Smalltalk 式クラスの多くには、対応する Smalltalk 名か総称名のどれかに対する typedef があります。この typedef は、プリプロセッサマクロ RW_STD_TYPEDEFS を定義することによって有効になります。typedef は自由に使用できますが、コードを保守しやすくするためには、実際のクラス名を使用するようお勧めします。


Smalltalk 式クラスの表

次の 2 つの表は、Tools.h++ Smalltalk 式クラスを要約したものです。表 12-1 は、17 のクラスすべてをその typedef、反復子、実装とともに示しています。表 12-2 は、クラス階層を図示しています。

表 12-1 Smalltalk 式コレクションクラス、反復子、typedef、および実装

クラス 反復子 "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 基底クラスを基準にしています。

RWCollectable
    RWCollection (抽象基底クラス)
        RWBinaryTree
        RWBTree
            RWBTreeDictionary
        RWBag
        RWSequenceable (抽象基底クラス)
            RWDlistCollectables (二重リンクリスト)
            RWOrdered
                RWSortedVector
            RWSlistCollectables (一重リンクリスト)
                RWSlistCollectablesQueue
                RWSlistCollectablesStack
        RWHashTable
            RWSet
                RWIdentitySet
                RWHashDictionary
                    RWIdentityDictionary


正しく理解できるよう、まず、例から始めましょう。次の例では、SortedCollection を使用して、RWCollectableStrings のセットを格納し、順序付けています。SortedCollection は、実際には Smalltalk 式コレクションクラス RWBinaryTree の typedef です。

そこに挿入されるオブジェクトは、仮想関数 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 行ずつ見て、何が起こるかを説明していきます。

//1 プリプロセッサマクロ RW_STD_TYPEDEFS を定義することによって、Smalltalk 式 typedef のセットが使用可能になります。これによって、RWBinaryTree という名前の代わりに、typedef された "SortedCollection" という名前が使用できるようになります。この 2 つの名前の内容は同一です。
//2 2 番目の #include は、クラス RWCollectableString を宣言します。これは、基底クラス RWCStringRWCollectable から多重継承する派生クラスです。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 へのポインタ) を返すためです。

str = (RWCollectableString*)sci()

これは実際のものへキャストしなければなりません。

//11 最後に、ポインタ str が逆参照されて出力されます。RWCollectableString の出力される機能は、基底クラス RWCString から継承されたものです。

このプログラムは実行時に 4 つの収集済み文字列を "順序正しく" 出力します。クラス RWCollectableString の場合、これは辞書式順序を意味します。


Smalltalk 式コレクションクラスの選択

前の節では、Smalltalk 式クラス、そのクラス階層、このクラスの動作例のリストを示しました。しかし、それらをどのように使用するかについては説明していません。この節では、各種の Smalltalk 式コレクションクラスについて概説します。実際の目的に適したものを選択する上での参考にしてください。選択に際しては、付録 A を参照することもできます。

バッグ、セット、ハッシュテーブル

クラス RWHashTable は最もわかりやすいクラスです。このクラスは、単純なハッシュ化ルックアップを使用し、特定のオブジェクトが格納されている "バケット" を見つけ、バケットの線形探索 (liner search) を行い、そのオブジェクトを見つけます。鍵となる概念は、同じ値を持つ (つまり、"isEqual" である) 複数のオブジェクトを 1 つのハッシュテーブルに挿入ができるということです。

クラス RWBagRWHashTable に似ていますが、同じ値を持つ複数のオブジェクトの出現回数を数えるという点が異なります。つまり、最初の出現のみが保持され、2 番目以降は単に "出現" 回数を増加させるだけです。このクラスは、挿入されるオブジェクトをキーとし、出現回数を値とするような辞書として実装されます。これは Smalltalk における "Bag" オブジェクトの実装方法です。なお、この実装は C++ の他の数多くの "Bag" クラスとかなり違っています。他の "Bag" クラスは、さらに RWHashTable クラスに近く、真の Bag ではありません。

クラス RWSet は 基底クラス RWHashTable と似ていますが、重複が許されないという点が異なります。つまり、ある値を持つオブジェクトを複数挿入しようとすると、重複するものの挿入が拒否されます。

クラス RWIdentitySetRWSet からの継承です。このクラスは、値の代わりに同一性にもとづいてオブジェクトを取り出します。これは RWIdentitySet がセットの一種であるため、各オブジェクトについて、インスタンスを 1 つしか挿入できません。

ハッシュテーブルにもとづくこれらのクラスのいずれにおいても、オブジェクトの順序は意味を持たないことに注意してください。順序が重要である場合は、順序付きクラスを選択してください。

順序付きクラス

RWSequenceable から継承するクラスは固有の順序を持っています。つまり、"6 番目または i 番目のオブジェクト" とか、"最初" または "最後" のオブジェクト、といった表現に意味があるということです。

これらのクラスは一般に、ベクトルとして、あるいは一重リンクリストまたは二重リンクリストとして実装されます。ベクトルにもとづくクラスは、スタックおよびキューの操作には向いていますが、途中への挿入には適していません。ベクトルにもとづくコレクションクラスの容量を超えて挿入を行うと、自動的にサイズ変更が行われますが、そのためにパフォーマンスの大幅な低下をもたらすこともあります。

バイナリクラスおよび B ツリークラスはソートされているので、固有の順序を持つ、という意味で、"順序付きクラス" とみなすことができるでしょう。しかしその順序は挿入の順序ではなく、収集されたオブジェクトの相対的な値によって内部的に決定されます。つまりオブジェクトを、ソートされたコレクションの中の希望する位置に任意に挿入することはできないのです (ソートされた状態が失われてしまうという問題があるからです)。したがって、これらのクラスは別にサブクラス化されます。

辞書

辞書 ("マップ" と呼ばれることもある) は、外部キーを使用して値を見つけます。キーと値の型は同じでなくてもかまいません (違っているのが普通です)。辞書は、キーと値とを関連付けるものであると考えることができます。たとえば、ユーザーがコンパイラでシンボルテーブルを作成する場合、シンボル名をキーとして使用し、その再配置アドレスを値として使用するでしょう。これはセットを使用して作成する方法と対照的です。セットを使用する場合、名前とアドレスを 1 つのオブジェクトにカプセル化する必要があります。

Tools.h++ には、RWHashDictionary (ハッシュテーブルとして実装) と、RWBTreeDictionary (B ツリーとして実装) という 2 つの辞書クラスがあります。キーと値は両方とも抽象基底クラス RWCollectable から継承しなければなりません。


RWCollection から継承される仮想関数

Smalltalk 式コレクションクラスの継承元は抽象基底クラス RWCollection であり、さらにその継承元は抽象基底クラス RWCollectable です (コレクションのコレクションを持つことが可能になります)。RWCollectable については 204 ページの「Smalltalk 式クラスの表」第 14 章「RWCollectable クラスの設計」で説明します。

抽象基底クラスとは、それ自体が使用されるのではなく、他のクラスによって継承されることを目的としたクラスのことです。このクラスを一種の仮想クラスと考えると、仮想関数の意味を簡単に投影することができます。抽象規定クラスの仮想関数は、派生クラスの機能の青写真を提供します。RWCollection は、抽象基底クラスとして、insert()remove()entries() などの各種の仮想関数を宣言することにより、コレクションクラスに対して青写真を提供します。

この節では、Smalltalk 式コレクションのすべてに継承される仮想関数を説明します。これらの仮想関数はどのコレクションでも理解されるものと考えられます。

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;
関数 isEmpty() は、コレクションの中にオブジェクトがまったく含まれない場合に TRUE を返します。関数 entries() は、コレクションの中に含まれているオブジェクトの総数を返します。

関数 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 行ずつ説明します。

//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*);
関数 remove() は、引数と等しい項目を検索し、コレクションからその項目を取り除いてその項目へのポインタを返します。該当する項目が見つからなければ、NULL ポインタを返します。

関数 removeAndDestroy()remove() と似ていますが、違うところは項目を返さずにすべての RWCollectable 項目に継承される仮想デストラクタを使用してそれを削除することです。この関数を使用するときは、項目は実際にヒープ (スタックではなく) から割り当てられており、他のコレクションと共有されていないということに注意しなければなりません。また、RWnilCollectable は キャッシュおいて RWCollectable を参照するので、削除しないように注意してください。

次の例は、前の例を拡張したものですが、仮想関数 removeAndDestroy() の使用を説明しています。

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);
最初の引数 RWapplyCollectable は typedef です。

typedef void  (*RWapplyCollectable)(RWCollectable*, void*);
つまり、RWapplyCollectable は、プロトタイプを持つ関数へのポインタです。

void yourApplyFunction(RWCollectable* item, void* x)
ここで、yourApplyFunction は関数の名前です。この関数はユーザーが与えなければなりません。この関数はコレクション内の各項目に対して呼び出され (そのコレクションに適した順序で)、最初の引数としてその項目へのポインタが渡されます。2 番目の引数 x は、apply() への呼び出しから渡されるもので、ユーザーが使用できます。たとえば、オブジェクトを描画するウィンドウへのハンドルを保持するために使用できます。

Smalltalk 式コレクションの apply() 関数が総称コレクションの apply() 関数と似ていることに注意してください (200 ページの「適用関数」と比較してください)。相違点は、ユーザー定義関数の最初の引数の型にあります。Smalltalk 式コレクションは RWCollectable* を使用するのに対して、総称コレクションは type* を使用します。いずれのコレクションでも、ポインタ item を正しい派生クラスにキャストするように注意する必要があります。

適用関数は一般に、コレクションの全メンバーを調べるために "最も効率の良い方法" を採用します。これは適用関数の最大の長所です。その短所は、若干使いにくいところがあり、ユーザーが別の関数を用意しなければならないということです 1

関数 clear() および clearAndDestroy()

コレクションからすべての項目を削除するには、関数 clear()clearAndDestroy() を使用することができます。

virtual voidclear();
virtual voidclearAndDestroy();
関数 clearAndDestroy() は、項目を削除するだけではなく、各項目について仮想デストラクタを呼び出します。この関数は注意して使用する必要があります。この関数は、同じ項目がコレクション内で複数回数発生するかどうかを (RWIdentitySet を内部的に作成することによって) チェックして、各項目を 1 回だけ削除します。ただし、項目が 2 つの異なるコレクション間で共有されるかどうかはチェックできません。特に、RWnilCollectable のインスタンスを保持するコレクション (ほとんどは共有される) に対しては、clearAndDestroy() を決して呼び出さないでください。また、コレクションのすべてのメンバーが、ヒープから割り当てられていることも確認する必要があります。


RWCollection クラスから共有されるその他の関数

RWCollection から継承されるすべてのクラスによって共有される関数は、他にもいくつかあります。これらの関数は仮想関数でないことに注意してください。

クラス変換

次の関数を使用すると、任意のコレクションクラスを RWBagRWSetRWOrderedSortedCollection (つまり、RWBinaryTree) に変換することができます。

RWBag         asBag() const;
RWSet         asSet() const;
RWOrdered     asOrderedCollection() const;
RWBinaryTree  asSortedCollection() const
これらの関数は類似の Smalltalk メソッドを模倣しているため、新しいコレクションのコピーを値によって返します。大きいコレクションでは、時間がかかりすぎる可能性があります。

他のコレクションの挿入と除去

これらの関数を使用すると、その引数の内容をそれぞれ挿入したり、削除することができます。

void  operator+=(const RWCollection&);
void  operator-=(const RWCollection&);

選択

関数 select() を次に示します。

typedef RWBoolean (*RWtestCollectable)(const RWCollectable*,
                   const void*);
RWCollection*     select(RWtestCollectable tst, void*);
関数 select() は、コレクション内の各項目ごとに、tst によって指される関数を評価します。この関数は、TRUE を返すような項目を自身と同じ型の新しいコレクションへ挿入し、そのポインタを返します。この新しいコレクションはヒープから割り当てられるので、ユーザーは後でそれを削除する必要があります。


RWSequenceable から継承される仮想関数

RWCollectable から継承する抽象基底クラス RWSequenceable から継承するコレクションには、固有の順序があります。この節では、この順序を使用する、RWSequenceable から継承した仮想関数について説明します。たとえば、次の仮想関数を使用すると、コレクション内の i 番目の項目にアクセスすることができます。

virtual RWCollectable*&       at(size_t i);
virtual const RWCollectable*  at(size_t i) const;
どのようなコレクションにおいても、最初の項目は i=0 の位置にあることを覚えておいてください。コンパイラは、コレクションが const を宣言したかどうかによって、使用する関数を選択します。関数の 2 番目に記述したものは 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
想像がつくように、これらの演算は、ベクトルとして実装されるクラス RWOrdered では非常に効率がよいのですが、リンクリストとして実装されるクラスに対してはそれほど効率がよいとはいえません。これは、特定のインデックスを見つけるためにリスト全体を調べなければならないためです。

次の仮想関数はそれぞれ、コレクション内の最初の項目か最後の項目を返します。ただし、コレクションが空であれば NULL を返します。

virtual RWCollectable*  first() const;
virtual RWCollectable*  last() const;
次の仮想関数は、引数に等しい最初のオブジェクトのインデックスを返します。ただし、このようなオブジェクトがなければ、特殊な値 RW_NPOS を返します。

virtual size_t  index(const RWCollectable*) const;
次に、index 関数の使い方の例を示します。出力は、検索中の変数のインデックスが位置 1 に見つかったことを示しています。

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);
次の例では、コードは関数 insertAt を使用して、位置 1 に 4 を挿入しています。

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);
この例では、target の仮想関数ではなく、コレクション RWCollectableString 内の member の仮想関数が呼び出されています。具体的には、次のようになります。

member.compareTo(&target);               // これが呼び出させる
target.compareTo(&member);               // こちらではない

ハッシュ

ハッシュとは、コレクション内からオブジェクトを見つけるための効率の良い方法のひとつです。それを使用するコレクションクラスのすべてが、全体的に同じ戦略をとります。まず、ハッシュテーブル内の正しいバケットを見つけるためにターゲットのメンバー関数 hash() が呼び出されます。バケットは一重リンクリストとして実装されます。同じバケットのすべてのメンバーが同じハッシュ値を持つので、完全に一致するものを見つけるためにはそのバケットにおいて線形探索を行わなければなりません。これを行うには、そのバケットの各メンバーを引数として、前述の候補のメンバー関数 isEqual() を呼び出します。真を返す最初の引数は、選択されたオブジェクトです。isEqual() が真かどうかをテストする 2 つのオブジェクトが異なるハッシュ値を持つように、クラスを設計しないように注意してください。このアルゴリズムは、このようなオブジェクトを処理できません。

一般に、ハッシュと線形探索のこの組み合わせと、ほとんどのハッシュアルゴリズムの複雑さにより、ハッシュコレクション内でのオブジェクトの順序はあまり意味を持ちません。したがって、apply() 関数または反復子がハッシュテーブルを走査するとき、オブジェクトが何か特定の順序で調べられるということはありません。



1 Smalltalk の世界で apply() に相当する関数は "do" です。この関数がとる引数は 1 つだけです (コレクションの中の各項目ごとに評価される一片のコード)。これは評価の対象となる方法とブロックを 1 箇所にまとめて保持するので、結果としてコードがすっきりしたものになります。この問題に限らず、C++ でのやり方はもっと面倒です。


Previous Next Contents Index