コンストラクタと例外

C++ のコンストラクタで例外が発生するとそのクラスのデストラクタが呼び出されず,メモリリークの原因となり兼ねないのでコンストラクタでは例外を発生させてはならない,と言う話があります.これに関しては [迷信] コンストラクタから例外を送出してはならないなどの反対意見もあるように,“コンストラクタで例外を発生させるなら自力で後始末せよ”が正しいようです.

これに関連して,コンストラクタで例外が発生した場合メンバ変数のデストラクタは呼ばれるのかどうかが自信がなかったので実験してみました.まず,例題その 1.

class error_example {
public:
    explicit error_example(int n) : p_(new int(n)) {
        std::cout << "constructor: " << *p_ << std::endl;
        throw std::runtime_error("something wrong");
    }
    
    ~error_example() {
        std::cout << "destructor of error_example" << std::endl;
        delete p_;
    }
    
    void print() { std::cout << *p_ << std::endl; }
    
private:
    int* p_;
};

/* ------------------------------------------------------------------------- */
//  main
/* ------------------------------------------------------------------------- */
int main(int argc, char* argv[]) {
    try {
        error_example obj(10);
        obj.print();
    }
    catch (std::runtime_error& e) {
        std::cerr << e.what() << std::endl;
        std::exit(-1);
    }
    
    return 0;
}

この実行結果は以下のようになります.error_example のデストラクタが実行されず,領域の開放が適切に行われていないことが分かります.

$ ./test
constructor: 10
something wrong

次に,error_example を以下のように書き換えてみます.

class error_example {
public:
    explicit error_example(int n) : p_(new int(10)) {
        std::cout << "constructor: " << *p_ << std::endl;
        throw std::runtime_error("something wrong");
    }
    
    ~error_example() {
        std::cout << "destructor of error_example" << std::endl;
    }
    
    void print() { std::cout << *p_ << std::endl; }
    
private:
    shared_ptr<int> p_;
};

そして,shared_ptr のデストラクタに適当に文字列を出力するようにして実行した結果が以下のようになります.

$ ./test
constructor: 10
ref_count: 1
delete object
something wrong

このように,初期化の終了したメンバ変数は,コンストラクタで例外が発生した場合でもデストラクタが呼ばれていることが分かります.そのため,領域の開放をメンバ変数自身がきちんと行うようになっていれば,メンバ変数の領域の解放漏れは防ぐことができるようです.