可変項目数リスト管理 TList.
要求.
プログラムを組んでいると、項目数が可変のリストが必要になることがよくあります。上限が決まっていれば普通に配列を使えば済むことですが、配列を使って上限を決めてしまうと、以下のような欠点が大きな問題になることがあります。
- 汎用ライブラリやカスタムコンポーネントなどのように、どの様な環境で使われるかわからないものでは、上限が決められない。決めてしまうと不便になる。
- 上限は比較的大きな数値だけど、ほとんどの場合はちょっとしか使わないような使用方法なら、メモリ効率が悪い。
このような場合の対処方法としては、以下のようなものが一般的ではないかと思います。
- C言語:reallocで上限を再割り当てする。あるいはmalloc/freeで同様の動作を行う。
- C++言語:new []で上限を再割り当てして内容をコピーし、古いものをdelete[]する。
コードを書いてみるとわかるのですが、毎回このコードを書くのは意外と面倒です。さらに、項目数が増えるたびに行われる再割り当てによる処理速度低下を回避するために、実際の項目数とは別に保持できる上限を用意する場合や、リストの途中に項目を挿入する、リスト中の任意の項目を削除するなどの機能が必要なら、面倒くささ倍増です。
解決.
VCLにはTListというクラスがあります。その名のとおりリストを管理するためのクラスで、前記の面倒な作業をまとめて肩代わりしてくれます。
実際にリストとして使われる各項目の型はケースバイケースなので、TListではvoid *で保持します。各項目の内容へはキャストしてアクセスする必要があります。TListの詳細はヘルプを参照してください。
例として、TListを使った場合のリスト項目の追加のコードは以下のようになります。TListSample/TListItemの定義を書いていませんが、適当に想像してください。
TListSample::TListSample() // サンプルのリストを使用するクラスのコンストラクタ { sampleList = new TList; // リストをコンストラクト } TListSample::addItem(void) { TListItem *newitem; // 新しく追加する項目 newitem = new TListItem; // ここで項目の内容を設定 sampleList->Add(newitem); // sampleListはすでにコンストラクトされているものとする }
リスト項目の削除は以下のようになります。
TListSample::deleteItem(int delIndex) { TListItem *delitem; // 削除する項目 delitem = (TListItem *)sampleList->Items[delIndex]; // 削除する項目へのポインタを取得 sampleList->Delete(delIndex); // リストから項目を削除 delete delitem; // 項目を破棄 } TListSample::~TListSample() { // すべてのリスト項目はすでに削除済みであるものとする delete sampleList; // リストを破棄 }
付記.
TList::Capacityプロパティは、リストの項目(であるポインタ)の、実際にメモリに確保している数を表しています。Capacityプロパティの値がCountプロパティの値を等しいときに、Add/Insertメソッドをコールすると、TListは勝手にCapacityプロパティを増加させます。どれだけ増加するかはその時のCapacityプロパティの値によって異なります。Capacityプロパティに代入を行わない条件で、C++Builder6で調べた結果は、以下のようでした。
増加前のCapacityの値 | 0 | 4 | 8 | 12 | 28 |
---|---|---|---|---|---|
増加後のCapacityの値 | 4 | 8 | 12 | 28 | 44 |
以降、Capacityプロパティは16単位で増加します。Delete/Extract/Removeを行っても、一度増加したCapacityプロパティが減ることはない様です。この仕様はTStringsも同じです。Capacityの増加をユーザが指定する方法は用意されていないようです。
ClearメソッドによってCapacity=0になります。TStringsはClearメソッドをコールしてもCapacityは変らないようです。
問題.
TListは便利で扱いやすいのですが、問題もあります。
Sort/CustomSortメソッドによってリストの内容をソートできるのですが、ソートにはTListが項目の大小関係を取得するためにコールバック関数を指定する必要があります。この辺りは標準Cライブラリ関数のqsortと同じです。
このコールバック関数への引数は、比較を行う2つの項目へのポインタ2つのみです。ソート条件などは項目とは別に保持されるのが普通でしょうから、ソート条件を渡すことが出来ません!
この問題への対処方法は以下のものが考えられます。
No. | 対処方法 | 対処方法の問題点 |
1 | ブロック外で宣言したstatic変数でソート条件を渡す。 つまりC言語的なモジュール化で解決する。 | 複数のスレッドから同時にコールされる可能性がある場合、MutexやSemaphoreなどで排他制御を行う必要がある(同期オブジェクトを使用可能)。 |
2 | ソート条件ごとにコールバック関数を用意し、条件に応じてSort/CustomSortに渡すコールバック関数を変える。 | ソート条件の組み合わせが多い場合、用意すべきコールバック関数の数が多くなるので、メモリ効率や生産性・保守性が低下する。 |
Sort/CustomSortメソッドの仕様は標準Cライブラリ関数のqsortを参考に決められたものではないかと推測できます。標準Cライブラリ関数であるqsortはこれでいいかもしれませんが、より進んだオブジェクト指向を実装したC++では、コールバック関数にthisポインタ(上記コードの例ではTListSampleのthisポインタ)を渡す手段があればよかったのにと思います。
Copyright 2005-2009, yosshie.