2005-09-29
デストラクタから例外を送出するような設計や実装は、いかなる事情があろうとも、決して行ってはいけません。デストラクタから例外を送出する可能性があるプログラムは信頼性とは無縁です。
では、なぜデストラクタから例外を送出してはいけないのでしょうか?一つの理由は、コンパイラ自体がデストラクタから例外が送出されることを前提として実装されていない場合があるからです。もう一つの理由は、ライブラリがデストラクタから例外が送出されないことを前提として実装されているからです。
それでは、自分が使っているコンパイラがデストラクタからの例外にきちんと対応しており、既存のライブラリを使わないのであればデストラクタから例外を送出してもよいのでしょうか?
そんなことはありません。
自動記憶域で宣言されたオブジェクトのデストラクタが例外を送出するかも知れない場合を考えてみましょう。
class X {};
struct A
{
~A()
{
throw X();
}
};
void foo()
{
A a;
throw X(); // ここで例外が発生
// a.~A(); // 二重例外発生!
}
上のサンプルコードを見てください。
デストラクタから例外を送出するクラスAのオブジェクトを自動記憶域で宣言し、その生存期間内で他の例外が発生した場合、A::~A()が呼び出されて、その中でまた例外が送出されます。これは二重例外と呼ばれ、問答無用でstd::teminateが呼び出されます。
つまり、デストラクタから例外が発生する可能性があると、そのプログラムは突然死する可能性が常に付いて回ります。これでは信頼性や安定性は期待できません。
次に、デストラクタから例外を送出するかもしれないオブジェクトを静的記憶域期間で宣言した場合を考えてみてください。静的記憶域のオブジェクトを監視ブロック(try〜catchの制御文)で囲む構文を記述することはできません。すなわち、こちらはデストラクタから例外が発生した時点で(std::terminateの呼び出しではなく)未定義の動作になります。
それでは、オブジェクトを動的記憶域にしか生成しない場合を考えてみましょう。この場合は確かに例外をコントロール可能です。しかし、そのオブジェクトに対するdeleteはいつ実行するのでしょうか?それが、他のクラスのデストラクタの中であれば、やはり問題があることに変わりありません。
また、動的記憶域に生成・解体できないクラスを実現することは簡単ですが、自動・静的記憶域に生成できないクラスを実現する方法はありません。関数の中で定義したローカルクラスでもない限り、こうしたクラスは非常に脆弱です。最初はどうにかなったとしても、修正を重ねるうちにいずれは二重例外の牙をむくことになるでしょう。
デストラクタから例外を送出しないために、デストラクタの中に監視ブロックを設けて、例外を封印してしまうようなコードを見かけることもあります。しかし、これではエラーを黙殺しているだけであり、やはり信頼性とは無縁のプログラムであるといわざるを得ません。
信頼性と安全性の高いプログラムを作成するためには、デストラクタや多重定義したdelete演算子、swap関数など、いくつかの関数からは決して例外を送出すべきではありません。
Comment