Copyright 1999 Rogue Wave Software
Copyright 1999 Sun Microsystems, Inc.
エラー処理 |
16 |
![]() |
Rogue Wave クラスライブラリはすべて、同じ、広範囲におよぶ完成されたエラー処理機能を使用します。このモデルでは、エラーは内部エラーと外部エラーの 2 つのカテゴリに大きく分類されます。内部エラーは、さらに回復可能または回復不可能のいずれかに分類され、プログラムの内部論理のエラーによって起こります。この種のエラーは通常、回復するのが難しく、プログラムを異常終了するのがデフォルトの応答です。外部エラーは、そのプログラムが予想していなかった事によって起こります。重要なプログラムは、外部エラーからいつでも回復できるようにしておく必要があります。
次の節では、Rogue Wave のエラーモデルを要約する表を示します。表に続いて、各種のエラーについて詳しく説明します。
Tools.h++ のエラーモデル
次の表は、Tools.h++ エラーモデルのエラーを分類して説明したものです。この表は、早見表として使用したり、参照として使用することができます。
エラーの種類 | 内部エラー | 外部エラー | |
---|---|---|---|
回復不可能 | 回復可能 | ||
原因 | プログラムの論理またはコードの欠陥 | プログラムの論理またはコードの欠陥 | プログラムが予想していなかった事 |
例 | 境界エラー、コレクションに NULL ポインタを挿入した | リンクリストの境界エラー、無効な日付を使用しようとした | 間違った日付を書き込もうとした、正則でない行列の逆行列を求めようとする、ストリーム書き込みエラー、メモリー不足 |
予測可能かどうか | 可能 | 可能 | 不可能 |
検出に要するコスト | 高い | 低い | 低い |
抽象レベル | 低い | 低い | 高い |
検出場所 | ライブラリのデバッグバージョン | ライブラリのデバッグバージョンと本番バージョン | ライブラリのデバッグバージョンと本番バージョン |
応答 | 回復機構なし | RWInternalErr から継承する例外を送出する | RWExternalErr から継承する例外を送出するか、オブジェクトの妥当性検査を行う |
内部エラー
内部エラーは、プログラム内の論理やコーディングの欠陥によって起こります。一般的な内部エラーは次のとおりです。
内部エラーは、エラー検出のコストと、そのエラーが実行時に検出されるかどうかによって、さらに次の 2 つに分類することができます。
なぜ、ライブラリはいくつかのエラーを回復不能と定義するのでしょうか。それは、エラーの検出には時間がかかるからです。性能上の理由から、ライブラリは、プログラムの一部に対して最小限の正確さだけを要求し、そして、そのレベルに達しないものだけをエラーであると判断します。エラーが回復不可能であるというのは、ライブラリの本番バージョンがそのようなエラーを検出する機構を持たない (したがって、エラーを回復することもない) という意味からです。
境界エラーは回復不可能です。なぜなら、インデックスが範囲内にあることを確認するためのコストは、配列へのアクセスそのもののコストをはるかに超える可能性があるからです。配列アクセスの多いプログラムでは、すべてのアクセスを検査すると、プログラムの処理速度が低下する可能性があります。これを避けるために、ライブラリは、ユーザーが常に有効なインデックスを使用するよう要求します。要求されている正確さが最小レベルであるので、回復不可能なエラーは比較的避けやすく、単純な概念のものです。
ライブラリのデバッグバージョンを使用してアプリケーションのコンパイルとリンクを行うと、回復不可能なエラーを簡単に見つけて除去することができます。詳細については、408 ページの「Tools.h++ のデバッグバージョン」を参照してください。デバッグバージョンには、コーディングエラーを発見するように設計された追加検査が多数含まれています。これらの検査には、余分な時間を要するものや、デバッグメッセージを出力するものまであるので、効率の良い最終製品を作成するには、本番バージョンを使用してコンパイルとリンクを行うとよいでしょう。
デバッグバージョンのライブラリがエラーを発見すると、通常は、プログラムを異常終了させます。
回復可能な内部エラー
回復可能な内部エラーは、予測しやすく低いレベルで起こるという点で回復不可能なエラーに似ています。しかし、次の点が異なります。
本番バージョンのライブラリは、検査のコストが比較的低いので、回復可能な内部エラーを検査することができます。たとえば、リンクリストの境界エラーを見つける場合、リスト内を検索するコストは、インデックスが境界内にあるかどうかを検出するコストをはるかに上回ります。したがって、アクセスのたびに境界エラーを検査することができます。
エラーが検出された場合、すでに説明したように、ライブラリは、RWInternalErr から継承する例外を送出します。次に、Tools.h++ が例外を送出した場合を示します。
// リンク "i" を探す。インデックスは範囲内になければならない RWIsvSlink* RWIsvSlist::at(size_t i) const { if (i >= entries()){ if(RW_NPOS == i) RWTHROW( RWBoundsErr( RWMessage( RWTOOL_NPOSINDEX))); else RWTHROW( RWBoundsErr( RWMessage( RWTOOL_INDEXERR, (unsigned)i, (unsigned)entries()) )); } register RWIsvSlink* link = head_.next_; while (i--) link = link->next_; return link; }
例外を送出することによって、例外を捕獲して、おそらくは回復する機会がユーザーに与えられます。しかし、プログラムの内部論理には疑いがあるので、作業中の文書を保存して、プログラムを中止するのがよいでしょう。
外部エラー
外部エラーは、プログラムが予想していなかった事が原因で起こります。概要で説明したように、重要なプログラムは外部エラーから回復できるようにしておく必要があります。外部エラーには次のものがあります。
外部エラーは実行時エラーの場合があります。オブジェクト指向環境では、実行時エラーは、無効なユーザー入力の結果、オブジェクトを無効な状態に設定しようという試みとして示される場合がよくあります。上記の例は、存在しない 1992 年 6 月 31 日という日付によって日付オブジェクトを初期設定する外部実行時エラーです。
外部エラーは、通常、オペレーティングシステムによって送出された例外という形式をとります。Tools.h++ は、これらの例外を検出して、獲得した資源を回復します。また、ファイルをクローズし、ヒープメモリーを復元します。しかし、ユーザーは、この種の例外中、Tools.h++ ライブラリの外部コードによって獲得された資源すべてに対して責任があります。一般に、Tools.h++ は、memcpy などのメモリ関連の C ライブラリ呼び出し中に、これらの例外が送出されないものと仮定します。Tools.h++ は、Tools.h++ オブジェクトまたはユーザー定義オブジェクトに対する操作中に発生する例外を検出しようとします。
理論上、Tools.h++ の外部エラーに対する応答は、例外の送出か、またはオブジェクト妥当性検査の実行です。プログラムを異常終了することはありません。しかし、実際には、コンパイラの中には例外を処理しないものがあるので、Tools.h++ は、エラーハンドラを回復する機会か、状態値をテストする機会を与えます。次に、isValid 関数を使用してユーザーの入力を検査する例を示します。
RWDate date; while (1) { cout << "Give a date: "; cin >> date; if (date.isValid()) break; cout << "You entered a bad date; try again\n"; }
xmsg RWxmsg RWInternalErr RWBoundsErr RWExternalErr RWFileErr RWStreamErr xalloc RWxallocこのように、この階層は 395 ページの「Tools.h++ のエラーモデル」で概要を述べたエラーモデルに相当します。この階層は、クラス xmsg の存在を前提にしています (通常はコンパイラベンダーによって供給されます)。これは、C++ 標準化委員会 X3J16 (92-0116 文書) のライブラリ作業グループによって標準化が検討されているクラスです。ご使用のコンパイラに xmsg と xalloc が付属していない場合は、Rogue Wave のクラス RWxmsg および RWxalloc がこれらをエミュレートします。
クラス xmsg は、何が間違っていたのかユーザーが見当をつけられるよう、捕獲側で出力できる文字列を渡します。この文字列は xmsg の特殊化バージョンによってフォーマットおよび国際化されます (370 ページの「メッセージの地域化」で説明しています)。
エラーハンドラ
Tools.h++ は、マクロ RWTHROW を使用して例外を発行します。コンパイラが例外をサポートしている場合、このマクロは、例外を発行する関数を呼び出すことによって解釈処理されます。コンパイラが例外をサポートしていない場合、マクロはエラーハンドラを次のプロトタイプによって呼び出すよう解釈処理されます。
void errHandler(const RWxmsg&);
typedef void (*rwErrHandler)(const RWxmsg&); rwErrHandler rwSetErrHandler(rwErrHandler);
#include <rw/rwerr.h> #include <rw/coreerr.h> #include <iostream.h> #ifdef RW_NO_EXCEPTIONS void myOwnErrorHandler(const RWxmsg& error){ cout << "myOwnErrorHandler(" << error.why() << ")" << endl; } int main(){ rwSetErrHandler(myOwnErrorHandler); // デフォルトエラーハンドラを // 獲得するためには、この行をコメントにする RWTHROW( RWExternalErr(RWMessage( RWCORE_GENERIC, 12345, "Howdy!") )); cout << "Done." << endl; return 0; } #else //RW_NO_EXCEPTIONS #error This example only for compilers without exception handling #endif |
Tools.h++ のデバッグバージョン
Tools.h++ ライブラリは、デバッグモードで作成することにより、各自のコード内の内部エラーを回復、訂正するための非常に強力なツールになります。
このライブラリのデバッグバージョンを作成するには、プリプロセッサフラグ "RWDEBUG" を定義してライブラリ全体をコンパイルしなければなりません。ライブラリとアプリケーション全体のコンパイルには、このフラグを定義するか定義しないかというどちらかひとつの設定を使用しなければなりません。結果として生成されるライブラリとプログラムは、やや大きくて遅いものになります。その他の指示については、該当する makefile を参照してください。
char buff[20]; char j = buff[20]; // 境界エラー
RWCString buff(20); char j = buff[20]; // 検出可能な境界エラー
char& RWCString::operator[](size_t i){ RWPRECONDITION(i < length() ); return rep[i]; }
次に、さらに複雑な例を示します。
template <class T> void List::insert(T* obj){ RWPRECONDITION( obj!= 0 ); head = new Link(head, obj); RWPOSTCONDITION( this->contains(obj) ); }
マクロ RWPRECONDITION と RWPOSTCONDITION は <rw/defs.h> に定義されていて、前処理マクロ RWDEBUG が定義されていなければ、非実行文としてコンパイルされます。次に、defs.h の内容を示します。
#ifdef RWDEBUG # define RWPRECONDITION(a) assert(a) # define RWPOSTCONDITION(a) assert(a) #else # define RWPRECONDITION(a) ((void*)0) # define RWPOSTCONDITION(a) ((void*)0) #endif