Previous Next Contents Index


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

よくある誤り

18


プログラミングエラーが起こる可能性をできるだけなくすように、ライブラリはあらゆる努力のもとに調整されてきました。それにもかかわらず、C++ はきわめて複雑な言語で、微妙な誤りを起こす可能性が無限にあります。

この章を書く際、技術サポート文書を検討することによって、ユーザーがこれまでに起こしてきた誤りの中で共通しているものを挙げました。さらに予防することのできる誤りについては、それが起こらないよう、ライブラリを書き換えるように努めました。これは常に最良の方法であるといえますが、常に可能であるとは限りません。たとえば、著しくパフォーマンスを低下させることがあります。あるいは、言語の制約から変更できないこともあります。

この章には、最も一般的な誤りのうち、こうした予防措置を行なった後も起こり得るものをまとめてあります。この章に目を通し、問題がある場合は、本書のほかの章もよく読んでください。Rogue Wave ライブラリは、プログラミングエラーの悩みをよく理解した開発者によって作成されたものです。


仮想関数の再定義

既存のクラスからサブクラスを作り、仮想関数を上書きする (無効にする) 場合は、上書きする関数が上書きされる関数とまったく同じ形式を持つようにしてください。これには "const" 修飾子も含まれます。

この問題が起こるのは特に新しい RWCollectable クラスを生成するときです。

例:

class MyObject : public RWCollectable {
public:
  RWBoolean isEqual();                            // "const" がない
};
コンパイラは isEqual() のこの定義を、基底クラス RWCollectable の中の isEqual() と完全に別個のものとして扱います。なぜなら、"const" 修飾子がないからです。したがって、ポインタを通して isEqual() を呼び出した場合は、次のようになります。

MyObject obj;
  RWCollectable* c = &obj;
  c->isEqual();	// RWCollectable::isEqual() が呼び出される


反復子

ANSI と ISO の標準 C++ ライブラリの草案により、現在 2 種類の反復子を Tools.h++ で使用することができます。つまり、本書で詳しく説明している従来の反復子と、新しい「標準ライブラリ」反復子です。標準で必須とされるようになった反復子の使用方法について詳細は、標準 C++ ライブラリに添付されているマニュアルを参照してください。この Tools.h++ のマニュアルでは、特に指示のないかぎり、反復子とは従来の反復子のことをいいます。

作成直後、Tools.h++ の反復子の位置が形式上は不定になっています。したがって、進めるか位置付けるかのどちらかを行わないと、反復子の位置は定義されません。確実な方法は、「進めてから戻る」です。コレクションの最後に達した場合、進んだ後の戻り値は特殊で、反復子の型に応じて false または NULL になります。

したがって、適切なコードは次のようになります。

RWSlistCollectables ct;
RWSlistCollectablesIterator it(ct);

.
.
.

RWCollectable* c;
while (c=it()) {
  // c を使用する
}


operator>>() の戻り型

よくある誤りは、以下の関数がヒープから結果を返すことを忘れることです。

Rwvistream&  operator>>(RWvistream&, RWCollectable*&);
RWFile&      operator>>(RWFile&,     RWCollectable*&);
これでは、ヒープから動的に割り当てた領域を指すポインタ変数が上書きされ、その領域が無効となって解放できなくなる (メモリーリーク) ことがあります。

main(){
   RWCollectableString* string = new RWCollectableString;
   RWFile file("mydata.dat");

   // 誤り:
   file >> string;                          // メモリーリーク

   // 正しい:
   delete string;
   file >> string;

}


ポインタの値コレクションの持続を避ける

場合によっては、ポインタを値にもとづくコレクションに収集して、値ではなく同一性を処理する方法が適していることがあります。しかし、値の意味論を使用するコレクションは、ポインタによって指される情報を格納するのではなく、ポインタの値をストリームに格納するので、それらのコレクションを持続しようとしてはなりません。これらの古いポインタをストリームから引き出して、メモリに戻した場合、それらのポインタはまず間違いなく無効な位置を指しているはずです。


インクルードパス

Tools.h++ ヘッダファイルへのインクルードパスを指定するときに、最後に "rw" を付けないようにしてください。

#  正しい:
CC -I/usr/local/include -c myprog.C

#  誤り:
CC -I/usr/local/include/rw -c myprog.C


メモリーモデルと他の修飾子の一致

プログラムを Rogue Wave ライブラリにリンクする場合は、修飾子、コンパイル「モード」マクロ (RWDEBUGRW_MULTI_THREAD など)、共有ライブラリを使用するかどうかの選択、メモリーモデルのすべてが一致することを確認してください。たとえば、フラットメモリーモデルを使用してコンパイルする場合は、必ずフラットメモリーモデルによってコンパイルされたライブラリにリンクしてください。同様に、デバッグバージョンのライブラリを使用する場合は、必ず同じデバッグ設定を使用してプログラムをコンパイルしてください (408 ページの「Tools.h++ のデバッグバージョン」を参照)。

そうしないと、リンカーから「未定義の外部参照」などのエラーが出されます。あるいは、プログラムが実行されても、期待どおりに実行されないおそれもあります。


関連するメソッドの整合性を維持する

Tools.h++ で使用されるクラスを設計する場合、「決して使用されないことがわかっている」ために、単純 hash メソッドや operator<() の提供などを省略したくなる場合があります。このような判断を下すと、後で保守を行うときに障害が生じるおそれがあります。正当な理由がないかぎり、operator<()operator==()compareTo()isEqual() などが同じ情報にもとづくようにする必要があります。また、相互に isEqual()== である値が同じハッシュ値を持つようにする必要もあります。そうしないと、これらの値をハッシュ技法を使用するコレクションに挿入したときに、検出できなくなります。最初の時点で少し手間をかけるだけで、デバッグに要する時間を大幅に削減することができます。


DLL

Tools.h++ の DLL バージョンはラージメモリーモデルを使用するので、そのモデルを使用するデータセグメントすべてを固定する必要があります。たとえば、データセグメントに RWCollectable オブジェクトを作成して、それを Tools.h++ コレクションに挿入した場合、そのコレクションは 4 バイトのポインタを保持することになります。データセグメントを移動した場合、ポインタは無効になります。したがって、.DEF 定義ファイルに次のようなコードを必ず入れてください。

DATA  PRELOAD FIXED
Microsoft がリアルモード WINDOWS をあきらめたことによって、固定データと大域メモリーの処理が以前ほど問題にはならなくなりました。プロテクトモードによって提供される追加レベルの間接演算を使用すると、セレクタを無効にしないで、物理メモリー間でデータを移動することができます。かわりに、デスクリプタテーブルのエントリが変更されます。


ライブラリ機能の活用

最も多く起こりうる誤りはライブラリ機能を最大限に使用しないことです。もし、何かちょっとした補助的クラスを書いていることに気がついたら、ちょっと立ち止まって考えてみてください。なぜそれが必要なのかと。自分の書いているものが少し雑然としていると思える場合にはもっと良い方法がある可能性があります。Tools.h++ のマニュアルの中に求めている情報が見つかるかもしれません。

よくある例を示します。

main(int argc, char* argv[]){
   char buffer[120];                   //オーバーフローの可能性あり
   ifstream fstr(argv[1]);
   RWCString line;

   while (fstr.readline(buf,sizeof(buf)) {
     line = buf;                                //余分なコピー
     cout << line;
   }
}
このプログラムは、コマンド行で指定されたファイルから行を読み取り、それを標準出力に出力します。RWCString クラスが持っている機能を最大限に活用すれば、これを大幅に単純化することができます。

main(int argc, char* argv[]){
   ifstream fstr(argv[1]);
   RWCString line;

   while (line.readLine(fstr)) {
     cout << line;
   }
}
このような例は他にも数え切れないほどあります。重要なことは、ユーザーが見てあまり整然としていないと思われるプログラムが実際に多いということです。




Previous Next Contents Index