比較的有名なサイトで「コンストラクタからの例外送出」が「禁じ手」として紹介されていることもあり、また、最近ではその内容を再編集した書籍が出版されたこともあって、コンストラクタから例外を送出すべきではないと考える人は多いようです。
その根拠となっているのは、コンストラクタから例外を送出した場合、デストラクタが呼ばれないためにリソースリークにつながるというものです。これは、次のようなケースを想定しているものと思われます。
foo::foo()
: a(new A), b(new B)
{
}
確かに、a または b のうち、後から初期化される側で例外が送出されると、他方が解放される機会が失われるため、リークにつながります。しかし、
void foo()
{
A* a = new A;
B* b = new B;
...
}
でも同じことが起こるのではないでしょうか。つまり、リークが起きるのは、コンストラクタから例外が送出されたからではなく、例外安全ではない設計またはコーディングに問題があるからです。
オブジェクトの生存期間というのは、コンストラクタの処理が完了した時点から始まります。生まれていないものが(デストラクタによって)死ぬことはナンセンスですし、生まれていないものに対して、何らかの操作を行うのも同じくナンセンスです。
また、コンストラクタが失敗したことを通知する一般的な手段は例外しかありません。明示的にコンストラクタが呼ばれる場合は、エラーを格納するためのオブジェクトを、参照またはポインタで渡すこともできるでしょう。しかし、関数からの return 時に呼び出されるコピーコンストラクタなど、それが不可能なケースもあるのです。
もし、Embedded C++ のように例外処理をサポートしない処理系の場合には、コンストラクタが失敗したことをデータメンバに記録しておき、後からそれを参照する方法などで逃げなければならないかもしれません。しかし、それは非標準処理系における特例に過ぎません。
ところで、最初の例に挙げたように、a と b の両方のデータメンバ初期化時に例外が送出される可能性がある場合はどうすればよいのでしょうか?
リークを回避するには次のようにします。
foo::foo()
try
: a(0), b(0)
{
a = new A;
b = new B;
...
}
catch (...)
{
delete a;
delete b;
}
しかし、可能であれば、a と b をそれぞれ new するのではなく、構造体にまとめることで、もっと簡単に状況を改善する方がよいと思います。
参考までに、最初は「コンストラクタから例外を送出すべきではない」としていたにもかかわらず、後にその主張を撤回しているページをご紹介したいと思います。
結論としては、コンストラクタが失敗した場合には例外を送出することによってそれを通知すべきであり、それ以外の一般的な方法はありません。
参考情報
トラックバック
ブックナビゲーション
- 技術情報
- Boost C++ Libraries メモ
- C++と組込み環境
- C++サンプル集
- C++テンプレート集
- C++プログラマのためのC言語入門
- C/C++迷信集
- [迷信] 'A'~'Z' の値は連続している
- [迷信] 0xe-0xe はゼロ
- [迷信] 1 バイトは 8 ビット
- [迷信] 2の累乗による割り算と右シフトは等価
- [迷信] FILE 型は構造体
- [迷信] abs は常に非負の値を返す
- [迷信] argv[0] はプログラム名
- [迷信] char 型は符号付き
- [迷信] double の出力書式は "%lf"
- [迷信] fflush で入力バッファをクリア
- [迷信] free でメモリを開放する
- [迷信] free に NULL を渡すとクラッシュする
- [迷信] gets は単純に fgets に置き換えられる
- [迷信] isalpha 関数の引数は char 型
- [迷信] new に失敗すると NULL が返る。
- [迷信] scanf ではバッファオーバーランを防げない
- [迷信] scanf でキーボードから入力
- [迷信] setjmp マクロの返却値は変数に代入できる
- [迷信] sizeof は定数式
- [迷信] void main(void)
- [迷信] とりあえず memset で初期化
- [迷信] アルゴリズム関数内で関数オブジェクトはコピーされない
- [迷信] オブジェクトの動的生成に失敗するとメモリリークする
- [迷信] コンストラクタから例外を送出してはならない
- [迷信] コンストラクタで自身をゼロクリア
- [迷信] コンパイラはプログラマの心を察してくれる
- [迷信] コンパイルエラーが出るのでアクセス指定子を修正
- [迷信] ソースコード中の即値を全廃せよ
- [迷信] ソースファイルの末尾に }
- [迷信] データ列のソートには qsort 関数を使うべし
- [迷信] プログラムは必ず main から始まる
- [迷信] 一重引用符の中には一文字しか書けない
- [迷信] 今どき int が 16 ビットの処理系なんて無い
- [迷信] 入力データ格納用配列のサイズは BUFSIZ
- [迷信] 割付けたメモリはプログラマが自分で解放しなければならない
- [迷信] 実数型とは浮動小数点型のことである
- [迷信] 引用符で囲んだヘッダ名はカレントディレクトリから探索する
- [迷信] 文字列から整数への変換には atoi
- [迷信] 構造体のタグ名は下線で始める
- [迷信] 構造体はクラスではない
- [迷信] 識別子に使える文字は英数字と下線のみ
- [迷信] 非局所オブジェクトは外部結合
- C言語再入門
- C言語徹底入門
- Drupal メモ
- TOPPERS 情報
- ライブラリ開発入門
- 分割コンパイルをきわめる
- 擬似プロセッサを作る
- 車輪の再発明
- 過去の情報


std::auto_ptr の出番では?
> リークを回避するには次のようにします。
挙げられているようなコンストラクタ try ブロックを使うのは
ごく稀で、ここは標準ライブラリである std::auto_ptr を使うのが
簡単で適切だと思います。この対策は先に挙げられた2つの例の
両方に対応できるものでもあります。
コメントありがとうございます。
new で割り付けたものだけに限れば auto_ptr でもよいと思います。
ただ、そのやり方だと、new 以外の方法で割り付けた場合に応用が利かなくなります(専用のスマートポインタを作ればよいのでしょうが...)。あるいは配列とか...。
本来であれば、コンストラクタの中で複数の割付けを行うのは避けた方がよいのでしょうね。
まず、最初の例なら class Foo { A
まず、最初の例なら
class Foo
{
A *theA_;
B *theB_;
// ...
};
ではなく、
class Foo
{
A theA_;
B theB_;
// ...
};
これではだめなのかを検討するべきでしょう。
だめなら、最初の匿名さんの意見のように auto_ptr<> 等を使うべきですね。
次に
> new 以外の方法で割り付けた場合
そんな煩雑なリソース管理が必要なら、その部分をそっくりそのまま別のクラスに分けるべきでしょう。そうすれば以上の方法が使えます。
> あるいは配列とか
std::vector<>ではなぜだめなのでしょうか、等々。
まぁ、結論の、objectの生成に失敗したら例外で通知せよ、という主張には全く同意なのですが。