例外が投げられるとき、例外安全なコードとは次の2点を満たすことである。
- リソース漏れを起こさない
- データ構造が無効な状態にならない
リソース漏れはsmart pointerなどリソース管理オブジェクトを適切に使うことで防ぐことができる。データ構造の保護に関しては次の3つのうち1つを保証しなければならない。
- 基本保証(basic guarantee)をする
- 例外が投げられてもすべての状態を有効に保つ
- 強い保証(strong guarantee)をする
- 例外が投げられた場合、その関数の呼び出し前のものに戻す
- 例外を投げない保証(nothrow guarantee)をする
- 例外を投げない
例外を投げない保証をするのが一番良いが、例外を投げないことが保証されている組み込み型の操作以外は一般に例外を出す操作が多く保証が難しい(STLのコンテナなど動的なメモリ確保を行うものはbad_alloc例外を出す可能性がある)。
なお、
void func() throw();
と例外を投げないと宣言してもC++では「funcが例外を投げたらそれは深刻なエラーなのでunexpected関数が実行される」という意味になる。例外安全かどうかは宣言ではなく実装で決まるのである。
void func() noexcept;
とするといかなる例外も送出しないことを保証できる。
多くの処理では投げない保証は難しいので基本保証か強い保証を考えることになる。強い保証を実現するための一般的な方法として「コピーと交換」が知られている。「コピーと交換」では
- 変更したいオブジェクトのコピーを作る
- コピーを変更する
- 変更が完了したら例外を投げない交換処理で元のオブジェクトと交換する
と実装する。コピーオブジェクトの変更中に例外が発生した場合は元のオブジェクトは変更を受けず、変更が完了した場合にのみオブジェクトが変更されることになり強い保証を実現することができる。
ただし、「コピーと交換」では変更ごとにオブジェクトのコピーを生成する必要があり性能問題が生じる可能性がある。その場合は基本保証をするべきである。
参考文献
Effective C++ 第3版, スコット・メイヤーズ著
noexcept(C++11), cpprefjp – C++日本語リファレンス