catch(...)とかwhen othersとか
こういう「すべての例外を受け取る」機能って、本当にすべての例外を受け取れるとは限りませんよね……。もちろん実装次第ではあるのですが。
具体的には「他の言語ランタイムから投げられた例外」。g++とgcjとGNATで関数ポインタ交換しあって例外投げまくったらどうなるかとかそういう話です。pthread_cancelとかその手のも。あとGNATですとAbort_Signalという特殊な例外は明示的にwhen Standard'Abort_Signalって書くと受け取れるのですがwhen othersには引っかかりません。違うコンパイラですとBorland C++のlongjmpは抜ける途中の関数にあるオブジェクトのデストラクタは動かしてくれますが、当然catch(...)にはかかりません。
で、そういうことを考えると、よくある↓のイディオムが使えなくなります。
nanika_t allocate_and_initialize() { nanika_t *p = new nanika_t; try{ initialize(p); return p; }catch(...){ delete p; /* initializeで例外発生 */ throw; } }
これだけですとauto_ptrやunique_ptrを使えと言われそうですが、メモリ管理に限らなければ、全部の例外を一度受けて巻き戻し処理を行って再度投げる形は、結構使われているはず。
try { /* なんかする */ } catch(...) { /* 実は全ての例外を取れないかもしれない */ /* 巻き戻し */ throw; }
なんで同ランタイムの例外しか受け取れないかといいますと、知らない例外を受け取っても例外オブジェクトの解体方法なんかがわかりませんし、バックトレース情報なんかも更新できませんし。
finallyやデストラクタは、例外オブジェクトの方に触れる必要がありませんので、gccでしたらdwarf、WindowsでしたらSEHみたいな共通の枠組みに則っていれば例外を投げたランタイムを問わずハンドリングできます。
というわけでfinallyが使えれば↓でいいのですが、
bool completed = false; try { /* なんかする */ completed = true; }finally{ if(!completed){ /* 巻き戻し */ } }
このcompletedって変数が嫌な感じです。break禁止な糞コーディング規約の元で無理やりフラグ立ててループを抜けているのと同じ感じがします。
というわけで↓みたいに書きたいなあ、という話でした。
try { /* なんかする */ } catch_all_and_rethrow { /* 巻き戻し */ }
常に再度投げるとわかっていれば、内部的にもcompletedみたいな変数は不要にできないかな……具体的には例外の種類を判別したりせずに一旦全部受けて、例外オブジェクトには一切手を付けないでそのまま_Unwind_Resume_or_Rethrowに任せたりでいけそうな。あ、でも/* 巻き戻し */の処理中に別の例外が発生したら、結局前の例外オブジェクトは解体せざるを得ませんので、お手上げですね……。