Wrapper Library for Windows MIDI APIのコードを読んでいて、コンストラクタから例外を投げていた。これっていいのだろうか?とか少し考えてしまった。
Webでなんか「コンストラクタから例外はご法度」とか見たことあるし、感覚的にはメモリ・リークしそうな「あぶないって感じ」はする。実際はどうなのだろう。
調べてみたら、「コンストラクタ中での初期化失敗を通知する方法は例外しかないので、例外は投げるべき」であることがわかった。コンストラクタ中で例外を投げてはいけないのは迷信であるとのこと。
[迷信] コンストラクタから例外を送出してはならない
http://www.kijineko.co.jp/tech/superstitions/dont-throw-exception-from-constructor.html
上はきじねこさんの記事からの引用で、例外を投げるA・Bクラスをポインタメンバにもつfooのコンストラクタである。http://www.kijineko.co.jp/tech/superstitions/dont-throw-exception-from-constructor.html
コンストラクタが失敗したことを通知する一般的な手段は例外しかありません。明示的にコンストラクタが呼ばれる場合は、エラーを格納するためのオブジェクトを、参照またはポインタで渡すこともできるでしょう。しかし、関数からの return 時に呼び出されるコピーコンストラクタなど、それが不可能なケースもあるのです。
リークを回避するには次のようにします。foo::foo() try : a(0), b(0) { a = new A; b = new B; ... } catch (...) { delete a; delete b; }
ぼくはC++を長いこと使用しているが精通していないので上記コードを見てちょっとの間???となってしまった。レベルが低くてすいません。
まず関数 tryという構文。これにより初期化子も含めた例外を捕まえることができるのである。
あとAはNewできてもBが失敗した場合、catch内でaをdeleteしてのいいのか?
これも問題ない。なぜならばNewに失敗しているのでbは0で初期化されたままであり、bが0(null)の場合deleteは何もしないからである。ということでこのコードはa,bで例外が発生してもメモリーリークを起こさないという訳だ。
ここで忘れていたのが、nullをdeleteすると何もしないことが規格で保証されていること。自分のコードでもpoinerをnullチェックしているような気がする。
Subaru-Gさんからコメントあり。こういう場合って普通a,bはスマポにするので関数tryとか使わなくてもa,bは安全に解放されますね。
例外処理、第 16 部でコンストラクトが失敗したオブジェクトってどういう状態なのかが書いてあった。
オブジェクトの存続期間は、そのコンストラクタが完了したときに始まります。コンストラクトで失敗したオブジェクトは存在しないのである。なのでコンストラクトに失敗した場合存在しない物として処理しなくてはならないし、コンストラクタで例外を送出するクラスは存在しないものとなるようにクラス設計・実装をしなくてはならないということかな?
必然的結果:コンストラクトが完了しなかったオブジェクトは存在しません。
必然的結果:生成の失敗を報告する唯一の手段は、例外を使ってコンストラクタを終了することです。