18.3 RWStringID
Rogue Wave ユーザから、RWClassID で可能な範囲よりも大きな範囲のクラス ID を、RWCollectable クラスで使えるようにしてくれないかという要請がたくさんありました。そこで、既存の多形永続性を持つファイルの逆互換性を保つために、RWClassID の意味を変更せずに、新しい種類のクラス ID である RWStringID を追加しました。
RWStringID は、次のいずれかの方法で RWCollectable クラスに関連付けることができます。そのクラスの RWStringID を選択するか、あるいはライブラリに自動的に RWStringID を生成させるかのどちらかの方法を取ります。自動的に生成した場合は、例えば、class MyColl のようにクラス名と同じ文字列になります。public RWCollectable によって自動的に RWStringID “MyColl” を取得できます。
固定された RWClassID と、以下のようにマクロ RWDEFINE_COLLECTABLE で生成された RWStringID でクラスを指定します。
RWDEFINE_COLLECTABLE(ClassName, ClassID) RWDEFINE_COLLECTABLE(MyCollectable1,0x1000) //例
固定された RWStringID と、次のようにマクロ RWDEFINE_NAMED_COLLECTABLE で 生成された RWClassID でクラスを指定します。
RWDEFINE_NAMED_COLLECTABLE(ClassName, StringID) RWDEFINE_NAMED_COLLECTABLE(MyCollectable2, "Second Collectable") // 例
上記例を使って、次にように書くことができます。
// 最初に試行を設定する
MyCollectable1 one; MyCollectable2 two;
// 実行中のすべての RWClassID は異なることが保証される
one.isA() != two.isA();
// 各 RWCollectable が RWStringID を持つ
one.stringID() == "MyCollectable1";
// ID を見つけるには、いくつかの方法がある
RWCollectable::stringID(0x1000) == "MyCollectable1";
two.isA() == RWCollectable::classID("Second Collectable");
18.3.1 識別子の寿命
同じ、または異なるプログラムの各実行ごとに多形永続性を与えるには、永続性を持たせる各クラスに永久の識別子が必要です。これまで、どんな RWCollectable でも永久の識別子は、その RWClassID を持っていました。RWCollectable から派生した各クラスごとに、RWDEFINE_COLLECTABLE によってクラスとその RWClassID を永久に関連付けるコードが生成されました。この識別方法は、そのまま存在しますが、現在のバージョンの Tools.h++ では、マクロ RWDEFINE_NAMED_COLLECTABLE を使って、選択した RWStringID をクラスに永久に連結することができます。
追加の RWStringID 識別子により、前の制約の元で可能だったものより、さらに ID が増え、RWCollectable にさらに説明的な ID を付けることが可能になりました。新しい識別子を取り入れるために、テンポラリの RWClassID は、デベロッパによって指定されたRWStringID を持つ各 RWCollectable クラスごとに生成されるようになりました。これらの RWClassID は、実行ファイルの実行中に必要に応じて構築され、その間は定数として留まります。ただし、これらの識別子は、別の実行ファイルあるいは別の実行中には異なる順序で生成されるので、永久的に格納するには適していません。
18.3.2 RWStringID でのプログラミング
RWCollectable には、新しい通常メンバ関数が 1 つ、静的メンバ関数が 2 つ加わりました。バージョン 6 に対してコンパイルされたオブジェクトとの互換性を保つことが、バージョン 7 Tools.h++ の主な目標の 1 つなので、これらの関数はいずれも仮想関数ではありません。そのため、リンクの互換性を無視した場合に比べて、これらの関数はやや性能が劣ります。
新しい通常メンバ関数は、次のとおりです。
RWStringID stringID() const;
The new static member functions are:
RWStringID stringID(RWClassID); // RWStringID をルックアップする RWClassID classID(RWStringID); // ClassID をルックアップする
RWFactory にも、次の新しい関数が含まれています。
void addFunction(RWuserCreator, RWClassID, RWStringID); RWCollectable* create(RWStringID) const; RWuserCreator getFunction(RWStringID) const; void removeFunction(RWStringID); RWStringID stringID(RWClassID) const; RWClassID classID(RWStringID) const;
Tools.h++ と一緒に出荷される RWCollectable と、前のバージョンとまったく同じように固定された RWClassID で定義されている RWCollectable を使うことができます。例えば、次の共通プログラミング記法を使うことができます。
RWCollectable *ctp; // ポインタを指定 if (ctp->isA() == SOME_CONST_CLASSID) // 特定のことを行う
ただし、ユーザ定義の RWStringID (つまり、非永久的な ClassID) を持つ RWCollectable を使う場合は、実行ファイルを実行するごとに RWClassID の値が異なることに注意してください。これらのクラスの場合、上の記法を置き換えることのできる記法が 2 つあります。
RWCollectable *ctp;
// ポインタを指定
// 比較に既存の RWCollectable を使う:
// RWStringID を比較するよりも、比較が高速になる
if(ctp->isA() == someRWCollectablePtr->isA())
// そのクラスインタフェースにコードを書くことができる
// ...
// 同定をハードコードするための記法。文字列比較は
// 整数比較よりも遅いので、やや遅くなる。
// また、stringID() は辞書のルックアップを使う。
if (ctp->stringID() == "Some ID String") {
// そのクラスインタフェースにコードを書くことができる
18.3.3 RWStringID の実装詳細
18.3.3.1 節から 18.3.3.5 節では、RWStringID の実装の詳細を説明します。仮想メソッドを使わずにどのようにして仮想関数の機能を追加するのかに関心がある場合、設計、効率性、およびその他の特定の問題に興味がある場合には、これらの節を読んでください。
18.3.3.1 自動 RWClassID
自動 RWClassID は、0×9200 〜 0xDAFF の範囲で未使用の RWClassID からシステム化された方法で作成されます。作成可能な RWClassID は 18,687 個あるので、極端なプログラムでないかぎり、RWClassID を使い切ることはまずありません。ただし、並外れたユーザに対応するために、あえてここで警告しておきます。自動的に生成された RWClassID を持つクラスは、1 つのプログラムで、18,688 個以上作成または使用することはできません。[1].
ただし、各クラスが持つことができる合計オブジェクト数とは何も関係ないことに注意してください。各クラスの合計オブジェクト数は、オペレーティングシステムとコンパイラの条件によってのみ制約されています。もちろん、0×8000 未満の完全なセットの RWClassID にもアクセスできます。さらに 32,767 個の RWCollectable を作成できますが、これらの ID は自動的には生成されないため、手動操作で指定しなければなりません。
18.3.3.2 静的メソッドを使って仮想メソッドを実装する場合
isA() 仮想メソッドは、「実行時に一意な」RWClassID を返すので、この 1 つの仮想メソッドを用いて各種のデータや関数ポインタが格納されているルックアップ表にインデックスを与えることができます (C++ に内蔵されている vtables を連想するかもしれません)。RWCollectable はすでに単一の RWFactory の存在に依存しているため、RWFactory インスタンスを使ってルックアップ情報を保管することにしました。
次の静的メソッドは、
RWStringID RWCollectable::stringID(RWClassID id);
RWFactory インスタンスで id をルックアップしようとします。関連付けられた RWStringID を見つけた場合は、それを返します。RWStringID が見つからなかった場合は、RWStringID(“NoID”) を返します。
次の静的メソッドは、
RWClassID RWCollectable::classID(RWStringID sid)
RWFactory インスタンスで sid と関連付けられている RWClassID があるかどうかを調べて、アナログ的に機能します。見つかった場合は、それを返します。見つからなかった場合は、RWClassID __RWUNKNOWN を返します。
18.3.3.3 多形永続性
RWCollectable の多形永続性は、新しい RWStringID クラスによっては影響を受けません。旧 RWClassID が変更されないかぎり、既存のファイルは、新しくコンパイルされリンクされた実行可能ファイルを使って、そのまま読み込むことができます。RWStringID を持つ新しいクラスは、古いクラスと自由に混在させることができます。永久的な RWClassID を持たない RWCollectable の格納サイズには、より大きな領域が必要となりますが、これによって他の RWCollectable の格納サイズが影響を受けることはありません。
同一 RWStringID を持つ RWCollectable が含まれているコレクションでは、同一 RWCollectable への複数参照でそれが最初に現われたときにだけ格納されるように、その RWStringID がストリームまたはファイルに一度だけ格納されます。
18.3.3.4 効率性
RWClassID は、速度とメモリ領域の両方において RWStringID よりも効率がいいため、できるかぎり引き続き RWClassID を使うことをお勧めします。RWStringID は、次の点で便利です。
- 整理上多くのプログラミンググループに一意の ID を作成する必要がある場合
- サードパーティライブラリで他のライブラリやユーザとの衝突を避ける必要がある場合
- 多少効率が悪くなっても RWStringID の記述機能が意味をなす場合
RWStringID は、現在バージョンの Tools.h++ でコンパイルされる RWCollectableクラスすべてに作成されます。この追加のコードは、RWStringID を使用しないプログラムに対してそれほど大きな影響を与えることはありません。RWClassID と RWStringID からのルックアップを保管するために RWFactory がより大きくなり、追加のデータを RWFactory に取り入れるために起動時間はやや長くなります。
18.3.3.5 識別子の衝突
RWStringID により識別子の衝突が緩和されますが、異なるクラスの RWStringID 間で衝突の可能性がまだ存在します。次の場合に、衝突が発生する可能性があります。
- 自動的に生成された RWStringID が、ユーザの選択した RWStringID と競合する場合
- 複数のクラスに間違って同一の RWStringID が指定された場合
- 異なる名前空間にある 2 つのクラスが同じ名前を持ち、そのため自動的に同じ RWStringID が生成された場合。これは、使用コンパイラが名前空間に対応していると仮定しています。
場合によっては、これらの衝突は重要ではありません。自動的に生成された RWClassID は、互いに異なること、および正規のユーザ指定の RWClassID とは異なることが保証されています。isA() と stringID() の各仮想メソッドおよびコンストラクタの RWClassID を基準にしたルックアップは、すべて引き続き正しく機能します。
ただし、衝突が問題が引き起こすような場合がいくつかあります。衝突するようなユーザ選択の RWStringID を持つクラスの多形永続性は、正しく機能しません。これらの場合、正しく格納されているとしても、データは回復不能です。同様に、RWStringID だけでクラス間を区別するようなユーザのコードは失敗します。
このような衝突は、避けることができます。まず最初に、他と衝突することはまずない RWStringID を使用すべきです。例えば、クラスの継承階層を表わす、またはリビジョン管理システムで使用されているように開発者名や会社名、作成時間、ファイルパスを埋め込んだ、RWStringID を使用します。そして、常に作成したプログラムをテストして、実際に RWStringID に関連付けられているクラスが期待したものであることを確認してください。
注釈
- ^ 16 ビットの DLL がメモリ上に読み込まれるときも、自動的に RWClassID が生成されます。