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

まず、なぜ仮想基底クラスを使用するのでしょうか?
簡単なポイントは、スペースを節約し、複数の継承条件下で同じオブジェクトを複数回構築しないようにすることです。

// 直接使用多继承
class A {
public:
    A() { cout << "A的构造函数调用" << endl; }
    ~A() { cout << "A的析构函数调用" << endl; }
};

class B {
public:
    B() { cout << "B的构造函数调用" << endl; }
    ~B() { cout << "B的析构函数调用" << endl; }
};

class C : public A, public B {
public:
    C() { cout << "C的构造函数调用" << endl; }
    ~C() { cout << "C的析构函数调用" << endl; }
};

class D : public A, public B {
public:
    D() { cout << "D的构造函数调用" << endl; }
    ~D() { cout << "D的析构函数调用" << endl; }
};

class E : public C, public D, public B {
public:
    E() { cout << "E的构造函数调用" << endl; }
    ~E() { cout << "E的析构函数调用" << endl; }
};

int main()
{
    E e;
}
// 结果如下, e的生命周期
A的构造函数调用
B的构造函数调用
C的构造函数调用
A的构造函数调用
B的构造函数调用
D的构造函数调用
A的构造函数调用
E的构造函数调用
E的析构函数调用
A的析构函数调用
D的析构函数调用
B的析构函数调用
A的析构函数调用
C的析构函数调用
B的析构函数调用
A的析构函数调用


// 使用虚拟基类后
class A {
public:
    A() { cout << "A的构造函数调用" << endl; }
    ~A() { cout << "A的析构函数调用" << endl; }
};

class B {
public:
    B() { cout << "B的构造函数调用" << endl; }
    ~B() { cout << "B的析构函数调用" << endl; }
};

class C : virtual public A, virtual public B {
public:
    C() { cout << "C的构造函数调用" << endl; }
    ~C() { cout << "C的析构函数调用" << endl; }
};

class D : virtual public A, virtual public B {
public:
    D() { cout << "D的构造函数调用" << endl; }
    ~D() { cout << "D的析构函数调用" << endl; }
};

class E : public C, public D, virtual public B {
public:
    E() { cout << "E的构造函数调用" << endl; }
    ~E() { cout << "E的析构函数调用" << endl; }
};

int main()
{
    E e;
}

// 结果,e的生命周期
A的构造函数调用
B的构造函数调用
C的构造函数调用
D的构造函数调用
E的构造函数调用
E的析构函数调用
D的析构函数调用
C的析构函数调用
B的析构函数调用
A的析构函数调用

このことから、A と B の 2 つの基本クラスの構築時間が短縮されていることが明確にわかります。

次に、それらの構築順序について説明し、最初に C++ ドキュメントに手順を投稿します.
ここに画像の説明を挿入
簡単に言えば、

  1. 最初に、基本クラスまたはオブジェクトの仮想基本ポインターを初期化します
  2. メンバーオブジェクトを初期化する
  3. 独自のコンストラクターを呼び出す

コード:

class A {
public:
    A() { cout << "A的构造函数调用" << endl; }
    ~A() { cout << "A的析构函数调用" << endl; }
};

class B {
public:
    B() { cout << "B的构造函数调用" << endl; }
    ~B() { cout << "B的析构函数调用" << endl; }
};

class C : public A, public B {
public:
    C() { cout << "C的构造函数调用" << endl; }
    ~C() { cout << "C的析构函数调用" << endl; }
    A a;
    B b;
};

class D : public A, public B {
public:
    D() { cout << "D的构造函数调用" << endl; }
    ~D() { cout << "D的析构函数调用" << endl; }
    B b;
    A a;
};

class E : public C, public D, public B {
public:
    E() { cout << "E的构造函数调用" << endl; }
    ~E() { cout << "E的析构函数调用" << endl; }
};

int main()
{
    E e;
}

// 结果,只看对象e的构造函数的顺序,析构的没放上来。
// E需要C、D、B三个基类的构造
A的构造函数调用
B的构造函数调用	// E的多继承中C的多继承基类的构造
A的构造函数调用
B的构造函数调用	// C中的A、B两个成员对象的构造
C的构造函数调用	// C本身构造函数调用
A的构造函数调用
B的构造函数调用	// E的多继承中D的多继承基类的构造
B的构造函数调用	
A的构造函数调用	// D中的B、A两个成员对象的构造
D的构造函数调用	// D本身构造函数调用
B的构造函数调用	// E的多继承中B的构造
E的构造函数调用	// E本身构造函数的调用

// 使用虚拟基类
class A {
public:
    A() { cout << "A的构造函数调用" << endl; }
    ~A() { cout << "A的析构函数调用" << endl; }
};

class B {
public:
    B() { cout << "B的构造函数调用" << endl; }
    ~B() { cout << "B的析构函数调用" << endl; }
};

class C : virtual public A, virtual public B {
public:
    C() { cout << "C的构造函数调用" << endl; }
    ~C() { cout << "C的析构函数调用" << endl; }
    A a;
    B b;
};

class D : virtual public A, virtual public B {
public:
    D() { cout << "D的构造函数调用" << endl; }
    ~D() { cout << "D的析构函数调用" << endl; }
    B b;
    A a;
};

class E : public C, public D, virtual public B {
public:
    E() { cout << "E的构造函数调用" << endl; }
    ~E() { cout << "E的析构函数调用" << endl; }
};

int main()
{
    E e;
}

// 结果
A的构造函数调用
B的构造函数调用	// C中的A、B两个成员对象的构造
A的构造函数调用
B的构造函数调用
C的构造函数调用	// C中的多继承基类的构造
B的构造函数调用
A的构造函数调用	// D中的B、A两个成员对象的构造
D的构造函数调用	// 虚拟基指针初始化指向前面构造过的A、B,无需重新构造
E的构造函数调用	// 最后虚拟基B类也是指向前面创建好的B,最后调用E的构造函数即可

デストラクタの順序は、最初
ここに画像の説明を挿入
に公式ドキュメントに記載されています。

  1. 非仮想基本クラス、簡単に言えば、オブジェクトは構築の逆の順序で破棄されます
class A {
public:
    A() { cout << "A的构造函数调用" << endl; }
    ~A() { cout << "A的析构函数调用" << endl; }
};

class B {
public:
    B() { cout << "B的构造函数调用" << endl; }
    ~B() { cout << "B的析构函数调用" << endl; }
};

class C : public A, public B {
public:
    C() { cout << "C的构造函数调用" << endl; }
    ~C() { cout << "C的析构函数调用" << endl; }
    A a;
    B b;
};

class D : public A, public B {
public:
    D() { cout << "D的构造函数调用" << endl; }
    ~D() { cout << "D的析构函数调用" << endl; }
    B b;
    A a;
};

class E : public C, public D, public B {
public:
    E() { cout << "E的构造函数调用" << endl; }
    ~E() { cout << "E的析构函数调用" << endl; }
};

int main()
{
    E e;
}

// 结果
A的构造函数调用
B的构造函数调用
A的构造函数调用
B的构造函数调用
C的构造函数调用
A的构造函数调用
B的构造函数调用
B的构造函数调用
A的构造函数调用
D的构造函数调用
B的构造函数调用
E的构造函数调用
// 析构顺序,同构造顺序相反,先析构本身,然后析构成员对象,最后析构基类
E的析构函数调用
B的析构函数调用
D的析构函数调用
A的析构函数调用
B的析构函数调用
B的析构函数调用
A的析构函数调用
C的析构函数调用
B的析构函数调用
A的析构函数调用
B的析构函数调用
A的析构函数调用
  1. 公式ドキュメントの画像である仮想基本クラスは、
    ここに画像の説明を挿入
    ここに画像の説明を挿入
    最初にコードを見てください。
class A {
public:
    A() { cout << "A的构造函数调用" << endl; }
    ~A() { cout << "A的析构函数调用" << endl; }
};

class B {
public:
    B() { cout << "B的构造函数调用" << endl; }
    ~B() { cout << "B的析构函数调用" << endl; }
};

class C : virtual public A, virtual public B {
public:
    C() { cout << "C的构造函数调用" << endl; }
    ~C() { cout << "C的析构函数调用" << endl; }
};

class D : virtual public A, virtual public B {
public:
    D() { cout << "D的构造函数调用" << endl; }
    ~D() { cout << "D的析构函数调用" << endl; }
};

class E : public C, public D, virtual public B {
public:
    E() { cout << "E的构造函数调用" << endl; }
    ~E() { cout << "E的析构函数调用" << endl; }
};

int main()
{
    E e;
}

// 结果
E的析构函数调用
D的析构函数调用
C的析构函数调用
B的析构函数调用
A的析构函数调用

一般的なプロセス:

  1. まず左の左は多重継承宣言の最初の基底クラスを指す. E の最初の基底クラスは C であり, C の最初の基底クラスは A. A はどのクラスも継承しない. これが順序: E > C > A
  2. 最後のAを思い出し、前のCにアクセスし、最初にステップ4と5でAを判断し、Aがリストにあるかどうかを判断し、そうでない場合は一番下に追加し、ある場合は何もしません(リスト:A)
  3. このとき、アクセスポイント C を操作し、次に継承されるクラスを見つけ、B を見つけます。B は仮想基底クラスであり、2 番目のステップに戻る必要はなく、ステップ 4 と 5 の判断を直接実行します。そうでない場合は、リストの一番下に追加します (リスト: A > b)
  4. アクセス ポイント C の作成を続行します。この時点で、C にはクラス操作のための基底クラスがありません。C の前のノード A にアクセスします。C、C は仮想基底クラスではなく、直接、判断せずに列挙します。(リスト: A > B > C)
  5. 次に、アクセス ポイント A を操作します。上記と同じ最終的なリストは、A > B > C > D > E であり、リストの順序で破棄します。

コードを使用して、理解するためにいくつかの制御実験を行いましょう

// 原实验
class A {
public:
    A() { cout << "A的构造函数调用" << endl; }
    ~A() { cout << "A的析构函数调用" << endl; }
};

class B {
public:
    B() { cout << "B的构造函数调用" << endl; }
    ~B() { cout << "B的析构函数调用" << endl; }
};

class C : virtual public A, virtual public B {
public:
    C() { cout << "C的构造函数调用" << endl; }
    ~C() { cout << "C的析构函数调用" << endl; }
};

class D : virtual public A, virtual public B {
public:
    D() { cout << "D的构造函数调用" << endl; }
    ~D() { cout << "D的析构函数调用" << endl; }
};

class E : public C, public D, virtual public B {
public:
    E() { cout << "E的构造函数调用" << endl; }
    ~E() { cout << "E的析构函数调用" << endl; }
};

int main()
{
    E e;
}
// 结果
E的析构函数调用
D的析构函数调用
C的析构函数调用
B的析构函数调用
A的析构函数调用

// 下面的代码只展示不同部分,太多重复的看得不爽
// 实验1:C不使用虚拟继承
class C : public A, public B {
public:
    C() { cout << "C的构造函数调用" << endl; }
    ~C() { cout << "C的析构函数调用" << endl; }
};

// 结果
E的析构函数调用
D的析构函数调用
C的析构函数调用
B的析构函数调用
A的析构函数调用 // C此时对B、A进行了析构,未使用虚拟基类会初始化,因此肯定要单独析构创建的对象,但顺序还是正确的。
B的析构函数调用
A的析构函数调用

// 实验2:改变E的C、D的生命顺序
class E : public D, public C, virtual public B {
public:
    E() { cout << "E的构造函数调用" << endl; }
    ~E() { cout << "E的析构函数调用" << endl; }
};

// 结果
E的析构函数调用
C的析构函数调用
D的析构函数调用	// 此时可以看到C、D的顺序相反,印证了算法是没错的
B的析构函数调用	// 但A、B顺序是没变的,因为我们没改变C、D的继承的声明顺序
A的析构函数调用

// 实验3:改变D的声明顺序
class D : virtual public B, virtual public A {
public:
    D() { cout << "D的构造函数调用" << endl; }
    ~D() { cout << "D的析构函数调用" << endl; }
};
// E没改
class E : public C, public D, virtual public B {
public:
    E() { cout << "E的构造函数调用" << endl; }
    ~E() { cout << "E的析构函数调用" << endl; }
};

// 结果,没有变化,因为B、A的顺序在C时就确定了,D的声明顺序不管紧要
E的析构函数调用
D的析构函数调用
C的析构函数调用
B的析构函数调用
A的析构函数调用

// 实验4:修改C的A、B的生命顺序
class C : virtual public B, virtual public A {
public:
    C() { cout << "C的构造函数调用" << endl; }
    ~C() { cout << "C的析构函数调用" << endl; }
};

class D : virtual public A, virtual public B {
public:
    D() { cout << "D的构造函数调用" << endl; }
    ~D() { cout << "D的析构函数调用" << endl; }
};

class E : public C, public D, virtual public B {
public:
    E() { cout << "E的构造函数调用" << endl; }
    ~E() { cout << "E的析构函数调用" << endl; }
};
// 结果
E的析构函数调用
D的析构函数调用
C的析构函数调用
A的析构函数调用
B的析构函数调用	// A、B的顺序相反了,因为他们的析构调用是根据C的声明顺序来的,改变C会改变A、B。

// 实验5:修改E的C、D的声明顺序,并改变D的声明顺序
class C : virtual public A, virtual public B {
public:
    C() { cout << "C的构造函数调用" << endl; }
    ~C() { cout << "C的析构函数调用" << endl; }
};

class D : virtual public B, virtual public A {
public:
    D() { cout << "D的构造函数调用" << endl; }
    ~D() { cout << "D的析构函数调用" << endl; }
};

class E : public D, public C, virtual public B {
public:
    E() { cout << "E的构造函数调用" << endl; }
    ~E() { cout << "E的析构函数调用" << endl; }
};
// 结果
E的析构函数调用
C的析构函数调用
D的析构函数调用
A的析构函数调用
B的析构函数调用	// 调用顺序也改变了

以上が私の理解です。

コンストラクターの公式ドキュメント リンク: https://docs.microsoft.com/zh-cn/cpp/cpp/constructors-cpp?view=msvc-170
デストラクタの公式ドキュメント リンク: https://docs.microsoft.com/ en -cn/cpp/cpp/destructors-cpp?view=msvc-170

おすすめ

転載: blog.csdn.net/A_easy_learner/article/details/125386907