17.5 デバッグバージョンの Tools.h++
Tools.h++ ライブラリをデバッグモードで構築し、コード内の内部エラーを発見して訂正するための強力なツールとして用いることができます。
デバッグバージョンのライブラリを構築するには、プリプロセッサフラッグ RWDEBUG を定義して、ライブラリ全体をコンパイルする必要があります。ライブラリ全体とアプリケーションを定義済みか未定義の単一設定のフラッグでコンパイルしなければなりません。その結果作成されるライブラリとプログラムは、ややサイズが大きく遅くなります。詳しい方法は、適切な makefile を参照してください。
フラッグ RWDEBUG は、重要な関数の最初と最後で、組になっている PRECONDITION と POSTCONDITION 節をアクティブにします。これらの前提および後置条件は、assert と共に実装されています。失敗すると、まずそれが発生したファイルと行番号とともに不正な条件を印刷したあと、プログラムを停止します。
17.5.1 RWPRECONDITION と RWPOSTCONDITION
Bertrand Meyer 著の『Object-oriented Software Construction』 では、これらの関数を呼び出し元と呼び出し先間の契約として提示しています。呼び出し元が一組の前提条件を堅守することに同意すると、呼び出し先が一組の後置条件を満たす結果を返すことを保証します。次の比較は、Tools.h++ において Meyer の範例が有益であることを示しています。まず、C における境界エラーを見てみましょう。
char buff[20]; char j = buff[20]; // 境界エラー!
C 言語でこのような境界エラーを検出するのは非常に難しいことですが、以下に示すように C++ 言語では簡単です。
RWCString buff(20); char j = buff[20]; // 検出可能な境界エラー
operator[] は明示的な境界検査を実行するように多重定義することができるため、C++ 言語では境界エラーを簡単に検出できます。つまり、フラッグ RWDEBUG が設定されている場合、以下のように operator[] も PRECONDITION 節を実行するということです。
char& RWCString::operator[](size_t I){
RWPRECONDITION(i < length() );
return rep[i];
}
この例の場合、operator[] が PRECONDITION が満たされていないことを検出するので、失敗に終わります。
次に、やや複雑な例を見てみましょう。
template <class T> void List::insert(T* obj){
RWPRECONDITION( obj!= 0 );
head = new Link(head, obj);
RWPOSTCONDITION( this->contains(obj) );
}
この例では、insert() 関数の役目は、引数によって指されているオブジェクトを T 型のオブジェクトへのポインタのリンクリストに挿入することです。この関数が機能するただ一つの前提条件は、ポインタ obj がヌルでないことです。この条件が満たされると、オブジェクトを問題なく挿入することを関数が保証します。後置条件節によってこの条件は調べられます。
プリプロセッサマクロ RWDEBUG が定義されていない場合は、マクロ RWPRECONDITION と RWPOSTCONDITION が < で定義され、コンパイルされます。以下は、makefile がどのように見えるかを示したものです。
#ifdef RWDEBUG # define RWPRECONDITION(a) assert(a) # define RWPOSTCONDITION(a) assert(a) #else # define RWPRECONDITION(a) ((void*)0) # define RWPOSTCONDITION(a) ((void*)0) #endif