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两个基类的构造次数减少了。

接下来说说他们的构造顺序,先贴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,先对A进行4、5步判断,A是否在列表内,不在则加入底部,在则不进行任何操作(列表:A)
  3. 此时操作访问的点C,找到下一个继承的类,找到B,B为虚拟基类,不需要回到第二步,直接进行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/zh-cn/cpp/cpp/destructors-cpp?view=msvc-170

猜你喜欢

转载自blog.csdn.net/A_easy_learner/article/details/125386907
今日推荐