web-dev-qa-db-ja.com

継承におけるコンストラクタ/デストラクタの呼び出し順序

オブジェクトの作成に関する小さな質問。次の2つのクラスがあるとします。

struct A{
    A(){cout << "A() C-tor" << endl;}
    ~A(){cout << "~A() D-tor" << endl;}
};

struct B : public A{
    B(){cout << "B() C-tor" << endl;}
    ~B(){cout << "~B() D-tor" << endl;}

    A a;
};

そして、主にBのインスタンスを作成します:

int main(){
    B b;
}

BAから派生し、A型のフィールドも持っていることに注意してください

私はルールを理解しようとしています。オブジェクトを構築するとき、最初にその親コン​​ストラクターを呼び出し、その逆の場合はそのことを知っています。

フィールド(A a; この場合)? Bが作成されると、いつAのコンストラクターを呼び出しますか?初期化リストを定義していませんが、何らかのデフォルトリストがありますか?そして、デフォルトのリストがない場合はどうなりますか?そして、破壊についての同じ質問。

38
Bug
  • 構築は常にベースclassで始まります。複数のベースclassesがある場合、構築は左端のベースから始まります。 (サイドノートvirtual継承がある場合、より高い優先度が与えられます)。
  • 次に、メンバーフィールドが構築されます。それらは宣言された順序で初期化されます
  • 最後に、class自体が構築されます
  • デストラクタの順序はまったく逆です

イニシャライザリストに関係なく、呼び出し順序は次のようになります。

  1. ベースclass Aのコンストラクター
  2. class Baという名前のフィールド(class A型)が構築されます
  3. class Bのコンストラクターの派生
75
iammilind

仮想/多重継承がないと仮定すると(かなり複雑になります)、ルールは単純です:

  1. オブジェクトメモリが割り当てられます
  2. 基本クラスのコンストラクターが実行され、ほとんどの派生クラスで終了します
  3. メンバーの初期化が実行されます
  4. オブジェクトはそのクラスの真のインスタンスになります
  5. コンストラクターコードが実行されます

覚えておくべき重要なことの1つは、ステップ4までは、オブジェクトはまだクラスのインスタンスではないということです。コンストラクターの実行が開始されてからこのタイトルを取得するからです。つまり、メンバーのコンストラクター中に例外がスローされた場合、オブジェクトのデストラクターは実行されず、既に構築された部分(メンバーや基底クラスなど)のみが破棄されます。これは、メンバーまたは基本クラスのコンストラクターでオブジェクトの仮想メンバー関数を呼び出す場合、呼び出される実装は派生したものではなく、基本的なものになることも意味します。覚えておくべきもう1つの重要なことは、初期化リストにリストされているメンバーは、初期化リストに現れる順序ではなく、クラスで宣言されている順序で構築されることです(幸運なことに、メンバーをリストすると、クラス宣言とは異なる順序で)。

また、コンストラクタコードの実行中にthisオブジェクトが既に最終クラスを取得している場合(たとえば、仮想ディスパッチに関して)でも、コンストラクタ実行を完了します。コンストラクターが実行を完了した場合にのみ、オブジェクトインスタンスはインスタンス間の本当のファーストクラスの市民です...その前は(正しいクラスを持っているにもかかわらず) "wanna-be instance"にすぎません。

破壊はまったく逆の順序で行われます。最初にオブジェクトデストラクターが実行され、次にそのクラスが失われます(つまり、オブジェクトのこの時点からベースオブジェクトと見なされます)。最も抽象的な親まで実行されます。コンストラクターについては、オブジェクトの仮想メンバー関数を(直接または間接的に)ベースまたはメンバーデストラクタで呼び出すと、クラスデストラクタが完了したときにオブジェクトがクラスタイトルを失ったため、実行される実装が親実装になります。

22
6502

基本クラスは常にデータメンバーの前に構築されます。データメンバーは、クラスで宣言された順序で構築されます。この順序は、初期化リストとは関係ありません。データメンバが初期化されるとき、パラメータの初期化リストを調べ、一致するものがない場合はデフォルトコンストラクタを呼び出します。データメンバーのデストラクタは、常に逆の順序で呼び出されます。

7
Vaughn Cato

基本クラスコンストラクターは、常にfirst.soを実行します。ステートメントを記述するときにB b;最初にAのコンストラクターが呼び出され、次にBクラスのコンストラクターが呼び出されます。したがって、コンストラクターからの出力は次のようなシーケンスになります。

A() C-tor
A() C-tor
B() C-tor
3
#include<iostream>

class A
{
  public:
    A(int n=2): m_i(n)
    {
    //   std::cout<<"Base Constructed with m_i "<<m_i<<std::endl;
    }
    ~A()
    {
    // std::cout<<"Base Destructed with m_i"<<m_i<<std::endl; 
     std::cout<<m_i;
    }

  protected:
   int m_i;
};

class B: public A
{
  public:
   B(int n ): m_a1(m_i  + 1), m_a2(n)
   {
     //std::cout<<"Derived Constructed with m_i "<<m_i<<std::endl;
   }

   ~B()
   {
   //  std::cout<<"Derived Destructed with m_i"<<m_i<<std::endl; 
     std::cout<<m_i;//2
     --m_i;
   }

  private:
   A m_a1;//3
   A m_a2;//5
};

int main()
{
  { B b(5);}
  std::cout <<std::endl;
  return 0;
}

この場合の答えは2531です。ここでコンストラクタを呼び出す方法:

  1. B :: A(int n = 2)コンストラクターが呼び出されます
  2. B :: B(5)コンストラクターが呼び出されます
  3. B.m_A1 :: A(3)が呼び出されます
  4. B.m_A2 :: A(5)が呼び出されます

同方向のデストラクタが呼び出されます:

  1. B ::〜B()が呼び出されます。すなわち、m_i = 2、Aでm_iを1にデクリメントします。
  2. B.m_A2 ::〜A()が呼び出されます。 m_i = 5
  3. B.m_A1 ::〜A()が呼び出されます。 m_i = 3 4 B ::〜A()が呼び出されます。m_i= 1

この例では、m_A1およびm_A2の構築は、初期化リストの順序ではなく、宣言の順序とは無関係です。

1
NEERAJ SWARNKAR

変更されたコードからの出力は次のとおりです。

A() C-tor
A() C-tor
B() C-tor
~B() D-tor
~A() D-tor
~A() D-tor
0
Caleb