Cでオブジェクト指向フレームワークを作る (2):例外処理の準備
オブジェクト指向とはあまり関係ないけど,例外処理について考えてみた。
CとC++の決定的な違いの1つが,例外処理のやり方。Cの場合は,基本的に関数の返り値を使うか,静的なエラーバッファの情報を使うか,あるいはその両者の組み合わせを利用する。C++はそれらに加えて,try{}catch(...){} を使って構造化された例外処理を記述することが可能である。
が,この例外処理(Googleコーディング規約では禁止されていたはず)は,あまり好きではない。それひとえに,AIXか何かRISCプロセッサのコンパイラで,例外処理を有効にしただけで10%以上速度が低下した,というトラウマがあるからである。
最近のプロセッサは十分速いとはいえ,try{}catch(...){} 関数の局所的なエラー処理に setjmp(みたいなことをしてるはず,多分)するのは,スマートじゃないよね。本当に必要なところで,大域的な処理がしたかったら setjmp して,関数内のエラー処理は古式ゆかしき goto を使えばいいのだ。これは十分Cで記述可能なわけで,だから,やっぱり例外処理についてもC++の機能なんて必要ないんじゃないかと思ってしまう。
あとは,try-catch で囲まれていない関数のデバッグが,黒ヒゲ危機一髪的な,何ていうか,ヒヤヒヤ感?に溢れるものになるのもいやだ。エラー処理の責任の所在が関数スコープに納められているCの方が,精神衛生上いいよね。
ただ,返り値を使うCのイケてないところは,真面目にエラー処理を行うと,たった1つのAPI呼び出しに平均で5行程度消費してしまうこと。
rc = some_function(...); if (rc) { handle_error(rc); goto END; }
みたいな。ただ,UIに直結したインタラクティブなコードでなければ,1つの関数で問題が起こった時の対応の仕方は概ね一種類しかないので,handle_error() の部分は括り出せて,こんな感じで書ける。
{ rc = some_function(...); if (rc) goto CATCH; rc = some_function(...); if (rc) goto CATCH; : goto FINALLY; CATCH: handle_error(rc); FINALLY: : }
さらに頑張って,
{ if ((rc = some_function(...))) goto CATCH; if ((rc = some_function(...))) goto CATCH; : goto FINALLY; CATCH: handle_error(rc); FINALLY: : }
くらいまでは,やってもいいかなーと思ってしばらくコードを書いてみたけど,if (...) goto CATCH; がひたすら繰り返されるのが,やっぱりばかばかしい。
あまりマクロは使いたくないけど,ここばかりは
#define xx_catch_if(func) do { if ((func)) goto CATCH; } while(0) /*xx_は名前空間だと思いねぇ*/ #define xx_catch_unless(func) do { if (!(func)) goto CATCH; } while(0)
とさせてもらった。でも,もうこれ以上のマクロの使用は止める。
チョットした工夫だけど,if (...) の中で代入を行うことで(MSVCでは警告を抑制する必要あり)ライブラリ特有の返り値の型に寄らず,同じ記法でエラー処理が書ける。
{ apr_status_t aprc; int rc; aprc = APR_SUCCESS; rc = 0; xx_catch_if(rc = some_function(...)); xx_catch_if(rc = some_function(...)); xx_catch_if(aprc = apr_some_function(...)); : goto FINALLY; CATCH: if (aprc) rc = xx_handle_apr_error(aprc, xx); else if (rc) xx_handle_error(rc); FINALLY: apr_pool_destroy(pool); return rc; }
例えばこんな感じ。取りあえずこんなもんで十分だろう。