構造関数と異常の分析について


コンストラクション関数内に異常が発生した場合、割り当てられたリソースは自動的に解放されません.たとえば、
class B{

public:

    B(){

        printf("into B constructor
"); } ~B(){ printf("into B destructor
"); } }; class C{ public: C(){ printf("into C constructor
"); throw std::runtime_error(" exception from C constructor"); } ~C(){ printf("into C destructor
"); } }; class A{ public: A(){ printf("into A constructor
"); } ~A(){ printf("into A destructor
"); } }; class D:A{ public: D():A(), b(NULL),c(NULL) { printf("into D constructor
"); b = new B(); c = new C(); } ~D(){ printf("into D destructor
"); delete b; delete c; } private: B *b; C *c; }; int main(int argc, char **argv){ D d; return 0; }

実行結果は次のとおりです.
into A constructor
into D constructor
into B constructor
into C constructor
terminate called after throwing an instance of 'std::runtime_error'
what(): exception from C constructor
オブジェクトcは、構築中に異常を投げ出し、ポインタbが指すメモリ空間は解放されない.
bのメモリをどのように解放しますか?まず、c++は、オブジェクトがどれだけ構築されているのか、どのリソースが解放されているのか、どのリソースが解放されていないのか分からないため、構造関数を呼び出すことはありません.もちろん、コンパイラはこれらの内容を記録することができますが、効率に深刻な影響を及ぼします.また、意味的には、c++は、オブジェクトのライフサイクルはコンストラクション関数が正常に終了してからコンストラクション関数が終了するまでの間であり、不完全なオブジェクトを構築することは生命のないものであり、存在しないものであり、存在しないオブジェクトに対してコンストラクション関数を呼び出すことはできないと考えています.コンパイラのデフォルトは、オブジェクトdのメモリ領域を解放するだけです.オブジェクトbが指すスタックメモリは、異常表示を用いて解放することができる
D():A(), b(NULL), c(NULL){

        printf("into D constructor
"); try{ b = new B(); c = new C(); }catch(std::runtime_error &e){ printf("into D constructor catch
"); delete b; b=NULL; delete c; c=NULL; }
}

実行結果は次のとおりです.
into A constructor
into D constructor
into B constructor
into C constructor
into D constructor catch
into B destructor
into D destructor
into A destructor
bが正常に解放されたので、もう一度変更して、bとcの構造を初期化リストに入れて、Dの構造関数を以下のように変更します.
D::D() : A(),b(new B()),c(new C())

   {

   }

私たちは異常を使い続けますが、有効ですか?
D() try:A(), b(new B()), c(new C()) {

        printf("into D constructor
"); }catch(std::runtime_error &e){ printf("in D constructor catch: %s
", e.what()); cleanup(); }

very niceに見えますね、走ってみてください.
into A constructor
into B constructor
into C constructor
into A destructor
in D constructor catch: exception from C constructor
into B destructor
into C destructor
*** glibc detected *** ./a.out: free(): invalid pointer: ***
ポインタが間違っています.同時に、catch文に入る前にベースクラスAが構造関数を実行していることがわかります.これは、catch文に達すると、構造関数の範囲から飛び出し、DとAのメンバーデータがアクセスできないことを示しています.
C++はなぜそうするのか,なぜ関数体外のcatch文でデータメンバーにアクセスできないのか.
まず,bとcが初期化されているかどうかは不明であり,未初期化ポインタの削除は不正である.
次に、bが初期化されていないことが分かると仮定し、bを削除し、catchでbとcにアクセスできれば(仮定)、Bのタイプを変更して、BにA*メンバーデータを含ませることを考慮します.
D() try:A(), b(new B(static_cast<A*>(this))), c(new C()) {

         printf("into D constructor
"); }catch(std::runtime_error &e){ printf("in D constructor catch: %s
", e.what()); cleanup(); }

Aは既に解析されており,bのデータメンバAは既に存在しないので,これは危険である.
c++は,ベースクラスでもデータメンバー構造でも失敗すると,オブジェクト構造全体が失敗し,半製品は存在しないと考えている.コンストラクション関数ブロック外のcatch文(上記)は、表示されずに例外を再放出しても、c++は自動的に放出され、最下層派生クラスオブジェクトの構造が停止するまで放出されます.例えば、catchはthrowを表示していません.main関数では:
try{    

        D d;

}catch (std::runtime_error &e){

        printf("int main: %s
", e.what()); }

Cコンストラクション関数から放出されたruntime_がキャプチャされます.error異常.
以上、以下のルールをまとめることができます
1.コンストラクション関数のtry-catch文は、スナップされた例外オブジェクトを変換するための1つの用途しかありません.
2.初期化リストにリソースを割り当てないで、コンストラクション関数内にリソースを割り当てる必要があります.
3.必ずtry-catch文でリソースを管理する場合は、コンストラクション関数内にあります.
4.RAII方式を使って資源を管理して、これは多くの脳細胞を死亡することが少なくて、このようにします
class D{

    auto_ptr<B> b;

    auto_ptr<C> c;

}

D::D() : A( ),b(new B()), c(new C())

{

}

討論を歓迎する.
 
リファレンス
http://www.gotw.ca/gotw/066.htm
《more effective c++》