コンストラクタ,デストラクタでの例外処理

この記事は,C++プログラマであるかを見分ける10の質問 - Life like a clown の「コンストラクタ,デストラクタにおける例外処理についての戦略を述べよ」に対する回答的な記事になります.例外処理は通常においてもなかなか難しい処理の一つですが,コンストラクタ,デストラクタにおける例外処理をどうするかは,さらに頭を悩ませる問題となります.

コンストラクタ

コンストラクタにおいて例外を発生させる場合にはデストラクタが実行されないため,気をつける事は「(デストラクタで行われるはずであった)各種リソースの開放を例外を送出する前にきちんとを行う」になります.コンストラクタで例外を発生させるべきではない派の主張の論拠も大体ここに起因するようです.

比較的有名なサイトで「コンストラクタからの例外送出」が「禁じ手」として紹介されていることもあり、また、最近ではその内容を再編集した書籍が出版されたこともあって、コンストラクタから例外を送出すべきではないと考える人は多いようです。

その根拠となっているのは、コンストラクタから例外を送出した場合、デストラクタが呼ばれないためにリソースリークにつながるというものです。

[迷信] コンストラクタから例外を送出してはならない | 株式会社きじねこ

コンストラクタで例外が発生した場合,そのクラス自体のデストラクタは実行されませんが,(初期化の終了した)メンバ変数のデストラクタは実行されます.したがって,例えば new でメモリを確保するようなメンバ変数の場合は,スマート・ポインタで保持しておく事によってその部分のリソース開放が自動的に行われ,リソースの開放忘れのかなりの部分を防ぐ事ができます.

尚,メンバ変数の初期化中に送出された例外は以下(outer クラスのコンストラクタ部分)のように記述することによって catch できます (function-try-block).

#include <iostream>

class inner {
public:
   inner(int n) { throw n; } // ここで例外
};

class outer {
   inner in;
public:
   outer(int n) try : in(n) {
       std::cout << "~outer()" << std::endl;
   } catch ( int err ) {
       // 例外を捕まえる
       std::cout << "caught at outer's ctor" << std::endl;
   }

   ~outer() {
       std::cout << "~outer()" << std::endl;
   }
};
http://ml.tietew.jp/cppll/cppll/thread_articles/9439

デストラクタ

一方,デストラクタにおいては,「デストラクタでは例外を送出しない」と言う方針を取る事がほとんどです.

  • C++ の仕様は、コンストラクタやデストラクタが例外を投げてはいけないとは書いていないし、「注意事項」さえ守れば投げてもいい。実際、コンストラクタが例外を投げるのはそれほど悪いことではない。
  • 一方でデストラクタが例外を投げるようなコードは大抵良くない。何故かというと、上記「注意事項」の一つであるところの「例外によるスタック巻き戻し中に別の例外を投げてはいけない(いわゆるダブルフォルトの禁止)」を抵触しないようにプログラムを書くのが難しくなるから。

・・・(中略)・・・

  • これと比較して、デストラクタで例外を投げた場合、少なくともクラス実装だけでダブルフォルトを防ぐことは不可能だし、そもそもデストラクタで例外を投げさえしなければ、まず絶対にダブルフォルトは起こらない。もちろん、デストラクタが例外を投げたとしてもダブルフォルトが起こらないようにプログラムすることは不可能ではないのだが、デストラクタが例外を投げてしまうばっかりに、覚えなければならない概念(=ダブルフォルト)が増えてしまう。これは、投げることによる利益と比較すると大きな代償だ。だから、デストラクタは例外を投げちゃいけない。
http://diary.imou.to/~AoiMoe/2007.12/early.html

尚,デストラクタでは例外を送出しない事を明示化するために例外指定 throw() を書く事がありますが,例外指定は C++0x で deprecate になったようです.そのため,今後の互換性を考えると書くかどうかは悩むところです.

例外指定をdeprecateにする。例外指定は、現実では、まったく意味がなかった。例えば、テンプレートが絡むと、明示的に投げる可能性のある例外を指定するなどということは、不可能になってしまう。

ただし、いくつかのコンパイラでは、何も指定しない、throw()を、関数は例外を投げないという指定とみなして、そのような最適化をしている。そこで、noexceptというキーワードを、新たに導入して、「この関数は例外を投げないと保証できる」と宣言出来るようにした。

本の虫: post-Pittsburgh mailingの簡易レビュー