今回は、「[迷信] コンストラクタから例外を送出してはならない」の続編ともいうべき内容です。コンストラクタで失敗した場合の通知方法には例外を用いるのが最良ですが、いろいろなケースを考えると不安になる方も多いようです。今回取り上げるのは次のようなケースです。
class A
{
public:
A()
{
throw 123;
}
};
int main()
{
A* p = 0;
try
{
p = new A;
}
catch (...)
{
}
delete p;
return 0;
}
この例では、クラス A を new で動的生成しようとしています。しかし、クラス A のコンストラクタは例外を送出しますので、動的生成に失敗し、catch 節に分岐してしまいます。クラス A のコンストラクタは失敗しましたが、operator new には成功するため、割り付けたメモリを解放する機会が失われるというわけです。
では本当にそんなことが起きるのか、上のコードに少し手を入れた次のコードを使って実験してみましょう。
#include <cstdio>
#include <cstdlib>
#include <new>
void* operator new(std::size_t size) throw(std::bad_alloc)
{
std::puts("new");
return std::malloc(size);
}
void operator delete(void* ptr) throw()
{
std::puts("delete");
std::free(ptr);
}
class A
{
public:
A()
{
std::puts("A::A");
throw 123;
}
};
int main()
{
A* p = 0;
try
{
std::puts("try");
p = new A;
}
catch (...)
{
std::puts("catch");
}
delete p;
return 0;
}
この実験コードでは、new および delete を再定義して、呼び出されたかどうかが分かるように標準出力に "new" または "delete" という文字列を出力するようにしています。これをコンパイルして実際に動かしてみると、
try new A::A delete catch delete
という結果が得られました。try から A::A までは説明の必要はないでしょう。そして、catch もどこで出力されたのか自明です。最後の delete も main からリターンする直前に行ったものです。では、上から 4 行目、A::A の次の delete は一体なんでしょうか?
実は、operator new が成功し、その後、生成しようとしたクラスのコンストラクタが例外を送出すると、自動的に operator delete が呼び出される仕掛けになっています。実際に呼び出される operator delete は、メモリの割り付けに使用した operator new と対になるものです。
operator new は、普通は std::size_t 型の引数を一つだけ受け取りますが、必要に応じて追加の引数を受け取る operator new を多重定義することが可能です。その場合、その operator new に対応した operator delete も必ず多重定義する必要があります。operator delete(void* ptr, const std::nothrow_t&) など、明示的に呼び出す機会はまずないような operator delete も、それに対応する operator new(std::size_t size, const std::nothrow_t&) があれば必ず定義しなければなりません。
これは、
p = new(std::nothrow) A;
とした場合に、クラス A のコンストラクタが例外を送出すれば、operator delete(void* ptr, const std::nothrow_t&) が呼び出されるからです。もし対応する operator delete が定義されていなければ、そのときは本当にメモリリークが発生してしまいます。
トラックバック
ブックナビゲーション
- 技術情報
- 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 情報
- ライブラリ開発入門
- 分割コンパイルをきわめる
- 擬似プロセッサを作る
- 車輪の再発明
- 過去の情報

