Version 13.05
エラーの返し方
「前回まではちょっと仕様まわりというか、プログラムを作る上での話とは
離れたことを説明しました」
『コーディングそのものにはあんま関係ないよね』
「ただ、アルゴリズム関数は何度も使うことが多いから、コントラクトって
考え方をしっかり持った方が後々役に立つからね」
『テキトーに作った関数をずっと使ってると色々ボロが出る、ってことね』
「で、今回からは実装について見ていきます。まずは、前回からの続きも兼
ねてエラー処理について」
『? それって難しいの?』
「場合によってはね。たとえば、前回の10倍する関数の場合には〈絶対に
1 〜 9の数にはならない〉から 1 をエラーの時の数にすることができたけ
ど、もし引数が実数だったら?」
『 0.1 とかあるから無理だね』
「でしょ。そういう場合にどうエラーを返すかっていう話。エラーを返す方
法は、大まかに分けると4つあります」
・戻り値としてエラーコードを返す。
・エラーコードを返す引数を渡してもらう。
・エラー用関数を使う。
・例外を投げる。
「まず、戻り値のはさっき言った〈エラーの時は 1 を返す〉っていう方
法」
『一番普通っぽいよね』
「ただ、エラーコードは普通に整数値を返すんじゃなくて、別に定数値とし
て定義した方がいいかな」
const int TENTIMES_WITHERROR2_E_OVERFLOW = 1;
int TenTimes_WithError2( int p_i )
{
if( p_i > 214748364 )
{
// エラー!
return TENTIMES_WITHERROR2_E_OVERFLOW;
}
return p_i * 10;
}
『そういえば前に言ってたね』
「 Version 4.09 ( No.059 ) で説明したよね。数字の 1 だと〈どういう意
味の 1 なのか〉わからないからね。この例だと 214748364 と 10 も定数値
にした方がいいかも」
『そこまでするんだ……』
「エラーコードを戻り値で返す方法は、簡単だけど実は問題点がいっぱいあ
ります」
・戻り値の型が void だと返せない。
・エラーコードを決めておく必要がある。
・戻り値の範囲によって返せるエラーコードが限られる。
『 void のって、 BOOL とかにすればいいんじゃないの?』
「それはそう。 BOOL みたいに、戻り値を〈エラーかどうかの情報を返すた
めだけに使う〉のが理想かな」
『でも10倍する関数にはそれは使えない、と』
「そうなると、エラーコードを決めて戻り値の中に混ぜることになります」
『まぜる?』
「普通に返される戻り値も、エラーコードも、同じ整数値でしょ。だから
〈この数はエラーコード〉っていうことをちゃんと決めておかないといけな
いわけ」
『コントラクトが成り立たなきゃいけないわけね』
「そういうこと。その場合には仕様書、つまりコメントやドキュメントがな
いと実質使えなくなります」
『それは困るね……』
「まぁエラーまわりはどれも仕様書がないとわからないんだけど、戻り値と
して返されるっていう点で他よりもわかりにくいかな」
『戻り値の範囲、って?』
「さっき言ったように、引数に実数を受け取ると」
『 1 はエラーコードに使えない……』
「戻り値にエラーコードを返すためには、正常時に〈絶対にあり得ない〉値
がないといけないし、その値の個数以上はエラーの種類を増やせないってい
う問題点があります」
『確かに、 1 〜 9 しか使えないってことになると、エラーコードの種類が
9個しか作れないってことになるんだね……』
「10倍する関数は、 11 とか -1 とかも返せないよ」
『あ”』
「でもまぁそういうこと。こういった問題点を解決するのが、エラーコード
を返すための引数を受け取るっていう方法」
const int TENTIMES_WITHERRORREF_E_OVERFLOW = 1;
int TenTimes_WithErrorRef( int p_i, int &p_riRetError )
{
p_riRetError = 0;
if( p_i > 214748364 )
{
// エラー!
p_riRetError = TENTIMES_WITHERRORREF_E_OVERFLOW;
return 0;
}
return p_i * 10;
}
『あ、引数が増えた』
「引数に、 int の参照を渡してもらうようにして、エラー時にはその参照
からエラーコードを返します」
void Use_TenTimes_WithErrorRef()
{
int iRef = 0;
TenTimes_WithErrorRef( 214748365, iRef );
if( iRef == TENTIMES_WITHERRORREF_E_OVERFLOW )
{
TRACE( "エラー!\n" );
}
}
『変数ひとつ作ってそれを第2引数に渡して、それをチェック……めんどい
ね』
「そう、参照で返す方法の最大のデメリットはその面倒さ。使うために必ず
エラーチェック用の変数を渡さなきゃいけないっていうのは嫌われるかな」
『ってゆーか、そういう関数って見たことないね。やっぱ嫌われ者?』
「たぶん。というわけであまり使われてません。対照的に比較的よく使われ
ているのが〈エラー用関数を使う〉っていう方法」
『エラー用の関数?』
「 API には SetLastError() と GetLastError() っていう関数が用意され
ています」
『ずっとランタイムの話してたのに API ……』
「まぁランタイムにも似たものがあるけど、こっちの方が有名ってことで」
const DWORD TENTIMES_WITHERRORSETTER_E_OVERFLOW = 1;
int TenTimes_WithErrorSetter( int p_i )
{
if( p_i > 214748364 )
{
// エラー!
SetLastError( TENTIMES_WITHERRORSETTER_E_OVERFLOW );
return 0;
}
return p_i * 10;
}
「 SetLastError() は、グローバルな領域にエラーコードをセットします。
このエラーコードの取得は GetLastError() で取得できます」
void Use_TenTimes_WithErrorSetter()
{
TenTimes_WithErrorSetter( 214748365 );
if( GetLastError() == TENTIMES_WITHERRORSETTER_E_OVERFLOW )
{
TRACE( "エラー!\n" );
}
}
『 SetLastError() でセットして GetLastError() で取得、ね』
「 API はこの方法を採用していて、たとえば Version 11.10 ( No.210 )
で紹介した GlobalAlloc() とか」
void Use_GlobalAlloc_Error()
{
GlobalAlloc( GMEM_MOVEABLE, -1 );
DWORD dError = GetLastError();
TRACE( "0x%X\n", dError );
// 0x8
}
「 GlobalAlloc() の第2引数は確保するサイズなので」
『マイナスはダメだよね……あ、エラーコード 8 だって』
「これを、 VisualC++ のツール【エラー検索】に掛けると」
『〈このコマンドを実行するのに十分な記憶域がありません。 〉って、
ちょっとわかりにくい』
「確かに……まぁ -1 サイズの領域を確保しようとして失敗した、ってこと
だね。こういうふうに、 API のエラーはエラーコードとして定義されてい
ます。ただ」
『ただ?』
「こうやってツールに掛けないとわからないし、どの API がどのエラー
コードを返すっていうのがドキュメントで明確になってません」
『げ!』
「それでも API を使う時には必須だったりするんだけどね」
『……でも、今まで出てこなかったよね……』
「エラー処理は省いてきちゃったからね……あともうひとつ問題があって、
今見たように API 用のエラーコードは先に決められていて、だから本当は
1 を SetLastError() しちゃいけないんです」
『げ』
「 SetLastError() のリファレンスを見るとわかるけど、していいのは 29
ビット目を立てた数。そういう細かいルールがあるから面倒かな」
『というわけで、本命の〈例外処理〉が出てくるわけね!』
「出てきません」
『え”』
「例外処理は上級向けなうえに、 C++ ではあまり使われていないので、こ
こでは説明しません」
『え〜!?』
「例外処理を説明する前にしなきゃいけない説明がたくさんあるから……と
いうわけで、結局は戻り値で返すのが一番簡単で広まってるからそれが一番
いいかな、と」
『危険だけど、ね!』
/*
Preview Next Story!
*/
『なんか結局丸め込まれてるし……』
「まぁ、今回のエラー処理は当分使わないけど」
『そなの?』
「次回からはエラーが少ないソートについてです」
『え、なんかソートって難しそうなイメージが……』
「というわけで次回」
< Version 13.06 アルゴリズムの定番・ソート >
『につづく!』
「エラーも含めて、特別なことが少ないから簡単だと思うよ」
『そなの?』
「……簡単ではないかも」
『げ』