c++におけるコンストラクタとデストラクタの例外

コード例

#include <iostream>
using namespace std;

class Base {
public:
	Base() {
		cout << "Base Constructor" << endl;
	}

	~Base() {
		cout << "Base Destructor" << endl;
	}
private:
};

class Sub : public Base {
public:
	Sub() {
		cout << "Sub Constructor" << endl;
		throw 1;
	}

	~Sub() {
		cout << "Sub Destructor" << endl;
	}
private:
};

int main() {
	Sub* o1 = NULL;
	Sub* o2 = NULL;
	try {
		o1 = new Sub;
		o2 = new Sub;
		delete o1;
		delete o2;
		return 0;
	}
	catch(int e) {
		cout << "Exception: " << e << endl;
		delete o1;
		delete o2;
		throw;
	}
}

何が起こるか?

  • コンストラクタで例外を投げると、
    • デストラクタは呼ばれない
    • 継承元のデストラクタは呼ばれる
    • newで確保されたオブジェクトは、コンストラクタで例外が投げられるとdeleteされる
    • 呼び元のnewの左辺の値は変更されない
  • デストラクタで例外を投げると、
    • 継承先のデストラクタが呼ばれず、deleteを並べることもできなくなるので、禁止

対応

  • コンストラクタで例外を投げず、初期化関数を分ける
    • コンストラクタで例外を投げるライブラリのクラスを継承できないので、だめ。
  • コンストラクタで例外を投げるが、丁寧に対応する
    • コンストラクタで確保したメモリは、コンストラクタのcatchで解放する。たぶんprivateでfinalize()メソッドを定義して使う。
    • newをtryで包み、複数objectを生成する場合、エラー処理ならcatch内でオブジェクトをdeleteする。通常でも解放する場合catch後にdeleteする。deleteはNULLを渡しても安全なので宣言時にNULL初期化をしておく。
      • 通常でも解放する場合、finalyがないので、解放処理が2箇所に分かれてしまう問題は、プラットホーム拡張のfinalyを使うか、あきらめる。
  • コンストラクタで例外を投げるが、shared_ptr等のスマートポインタを利用する