[迷信] コンストラクタから例外を送出してはならない

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

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

foo::foo()
  : a(new A), b(new B)
{
}

確かに、a または b のうち、後から初期化される側で例外が送出されると、他方が解放される機会が失われるため、リークにつながります。しかし、

void foo()
{
  A* a = new A;
  B* b = new B;
  ...
}

でも同じことが起こるのではないでしょうか。つまり、リークが起きるのは、コンストラクタから例外が送出されたからではなく、例外安全ではない設計またはコーディングに問題があるからです。

オブジェクトの生存期間というのは、コンストラクタの処理が完了した時点から始まります。生まれていないものが(デストラクタによって)死ぬことはナンセンスですし、生まれていないものに対して、何らかの操作を行うのも同じくナンセンスです。

また、コンストラクタが失敗したことを通知する一般的な手段は例外しかありません。明示的にコンストラクタが呼ばれる場合は、エラーを格納するためのオブジェクトを、参照またはポインタで渡すこともできるでしょう。しかし、関数からの return 時に呼び出されるコピーコンストラクタなど、それが不可能なケースもあるのです。

もし、Embedded C++ のように例外処理をサポートしない処理系の場合には、コンストラクタが失敗したことをデータメンバに記録しておき、後からそれを参照する方法などで逃げなければならないかもしれません。しかし、それは非標準処理系における特例に過ぎません。

ところで、最初の例に挙げたように、ab の両方のデータメンバ初期化時に例外が送出される可能性がある場合はどうすればよいのでしょうか?

リークを回避するには次のようにします。

foo::foo()
try
  : a(0), b(0)
{
  a = new A;
  b = new B;
  ...
}
catch (...)
{
  delete a;
  delete b;
}

しかし、可能であれば、ab をそれぞれ new するのではなく、構造体にまとめることで、もっと簡単に状況を改善する方がよいと思います。

参考までに、最初は「コンストラクタから例外を送出すべきではない」としていたにもかかわらず、後にその主張を撤回しているページをご紹介したいと思います。

C と C++ での例外処理 第 16 部

結論としては、コンストラクタが失敗した場合には例外を送出することによってそれを通知すべきであり、それ以外の一般的な方法はありません。

参考情報

コンストラクタの関数tryブロックのハンドラ

はじめまして。

「リークを回避するには次のようにします」の例ですが、2003年の C++ の規格の 15.3 Handling an exception の第10段落に、

Referring to any non-static member or base class of an object in the handler for a function-try-block of a constructor or destructor for that object results in undefined behavior.

とありますので、この例のように catch 節の中で a や b にアクセスするのは undefined ということになるのではないでしょうか。

この例であれば、関数 try ブロックをやめて、普通のローカル try ブロックにして、再 throw すればよさそうに思えます。

ご検討ください。

std::auto_ptr の出番では?

> リークを回避するには次のようにします。

挙げられているようなコンストラクタ try ブロックを使うのは
ごく稀で、ここは標準ライブラリである std::auto_ptr を使うのが
簡単で適切だと思います。この対策は先に挙げられた2つの例の
両方に対応できるものでもあります。

コメントありがとうございます。

new で割り付けたものだけに限れば auto_ptr でもよいと思います。
ただ、そのやり方だと、new 以外の方法で割り付けた場合に応用が利かなくなります(専用のスマートポインタを作ればよいのでしょうが...)。あるいは配列とか...。
本来であれば、コンストラクタの中で複数の割付けを行うのは避けた方がよいのでしょうね。

C++0x なら std::unique_ptr

C++0x なら std::unique_ptr で解決ですね。
それまでは boost::interprocess::unique_ptr で。

まず、最初の例なら class Foo { A

まず、最初の例なら

class Foo
{
A *theA_;
B *theB_;
// ...
};

ではなく、

class Foo
{
A theA_;
B theB_;
// ...
};

これではだめなのかを検討するべきでしょう。

だめなら、最初の匿名さんの意見のように auto_ptr<> 等を使うべきですね。

次に
> new 以外の方法で割り付けた場合
そんな煩雑なリソース管理が必要なら、その部分をそっくりそのまま別のクラスに分けるべきでしょう。そうすれば以上の方法が使えます。

> あるいは配列とか
std::vector<>ではなぜだめなのでしょうか、等々。

まぁ、結論の、objectの生成に失敗したら例外で通知せよ、という主張には全く同意なのですが。

このエントリーを含むはてなブックマーク