月曜日, 3月 10, 2008

C++解説15(Exception1)

プログラムが異常した場合の処理をどのように記述してきたか?
C++以前の言語とC++以降の言語では
この辺りが大きく違うのではないでしょうか?

まあ、今回説明するExceptionと言うものの
元祖がC++なのか良く分かっていないのですが
私自身はC++で始めて出合った構文です。


この機能の素晴らしさはC言語プログラマであれば
絶対に分かる機能だと思います。

プログラムが異常した場合、終了させる方法は2つあります。
1つ目は「abort()」という手段で終わらせる方法
2つ目は「main」を正常復帰値以外で終わらせる方法です。

1つ目の方法は全てを破棄する方法ですから
特別な制御は不要です。

どのようなプログラムを作成するかによりますが
私自身はあんまり行わない終了方法です。
まあ私自身はオンラインプログラムが多く
しかもクラッシュさせる事が
絶対にできないシステム開発ばかりであったため
「abort()」という選択肢はありませんでした。

そのため必然的に2つ目の方法である
「main」の異常復帰という方法を取りました。
もちろん、データ不正などデータベース等にログしているデータが
不正の場合、続行は不可能です。
こういった場合、その影響範囲を最小にするための
制御としてクラッシュさせるわけにいかなかったと言う事です。

少し勉強した時はC言語の標準関数は「errono」に値を返す事を
利用してプログラムを組んだ時期もありましたが(こっちは趣味です)
基本的にはライブラリを使う人間としては
なぜライブラリの使用ができなかったのかというエラーは
あんまり重要ではない事がほとんどでした。

要するに、失敗したという事実が確認できれば十分であると言う事です。
そうなってくると「errono」の使用は少しづつ行う事をしなくなりました。

すると関数を呼び出し、その復帰値を参照し問題なければ次に。
また別の関数を呼び出し、また復帰値を参照し問題なければ次に。
という方法でプログラムを書く事が普通となります。

これは当り前なのですが、実はC言語では諦めている境地で
C++ではこんな方法はありえないと言う事になります。

例として制御関数「ctl_main()」が「int funcA()」「int funcB()」「int funcC()」
を呼び出ししているプログラムというものは
基本的には以下の通りになります。

int ctl_main() {
int nRet;

nRet = funcA();
if (nRet != 0) {
return nRet;
}

nRet = funcB();
if (nRet != 0) {
return nRet;
}

nRet = funcC();
if (nRet != 0) {
return nRet;
}

return nRet;
}

これは当たり前のようで物凄く読み辛いプログラムです。
異常系のロジックが半分以上埋め込まれている事がその原因です。

これをC++では以下の通りで表現する事ができます。

「void funcA() throw(string)」
「void funcB() throw(string)」
「void funcC() throw(string)」

int ctl_main() {
try {
funcA();
funcB();
funcC();
} catch(string e) {
cout << e << endl;
return -1;
}

return 0;
}

これは「funcA」「funcB」「funcC」が異常発生の時に
例外を「throw(発生させる)」するので、
異常系処理を行う必要がある場合に、
その例外を「catch(例外処理を行う事を宣言)」して
処理を行う。

例えば、あんまり良い例ではないが1例示します。
「funcA」「funcB」「funcC」が「引数:char」を取り、
以下の事をcheckする関数とします。
funcA:数字であるか判定
funcB:偶数であるか判定
funcC:3の倍数であるか判定

上記の3関数はCheck結果がNGとなる場合、例外を発生させます。

void funcA(char c) throw(string) {
if(('0' <= c) && (c <= '9')) {
} else {
throw string("Param is not Numeric.");
}
}

void funcB(char c) throw(string) {
if (c % 2) {
throw string("Param is not even.");
}
}

void funcB(char c) throw(string) {
if (c % 3) {
throw string("Param is not multiple of three.");
}
}

int ctl_main() {
try {
funcA();
funcB();
funcC();
} catch(string e) {
cout << e << endl;
return -1;
}

return 0;
}

これはthrowされた内容が出力されて異常終了する事になります。
この記述の優れているところは、
正常系処理の中に異常系処理を書く必要がなく
非常にプログラムが見やすいというところです。

次回は具体的な使い方を見ていきます。

0 件のコメント: