C++普通类,派生类,虚基类的成员构造顺序以及构造函数调用顺序详解
创始人
2024-05-16 04:20:57
0

目录

    • 前言
    • 普通类构造析构顺序
      • 解析
      • 依赖关系产生的错误
    • 派生类构造析构顺序
      • 解析
      • 扩展菱形多继承场景
    • 含虚基类的派生类构造析构顺序
      • 解析
      • 扩展菱形多继承场景(引入虚继承)

前言

C++规定“对象的析构过程必须与其构造过程相反”这一语法规则。

因此我们研究透彻了构造过程,那么析构过程自然就是相反的;

(因为一个类的成员初始化和析构,类似一个压栈与弹栈的过程)

普通类构造析构顺序

  • 按照成员变量生命的顺序构造
#includeint i = 1;using namespace std;
class A 
{
public:A():a(i++),b(i++) {} //i为全局变量,初始值为1int b;int a;
};
int main()
{A x;cout << x.a <<' '<< x.b << endl; //运行结果2  1 ;return 0;
}

可以看到,虽然我们在类型A默认构造中的初始化列表里指定了构造顺序: 先构造a成员,再构造b成员;

事实上还是按照成员变量的声明顺序,先构造了b成员为1,再构造了a成员为2!

解析

对象的析构过程必须与其构造过程相反

一个类有不止一个构造函数,但是只有一个析构函数!

如果这些构造函数对成员的初始化顺序各不相同,那么在析构这个类的对象时,应当遵循什么样的成员析构顺序呢?难到要对构造改类对象时的初始化顺序进行某种方式的记录吗?

实际上,析构函数是不会关心这个类的对象是使用哪个构造函数构造出来的,但同时又要匹配一个确定的析构顺序,那么显然编译器只能根据类声明中成员变量的出现顺序来制定成员变量的初始化顺序紧接着相反的析构的顺序也就确定了;

依赖关系产生的错误

若成员的初始化存在某种依赖时应当在类声明中给出注释以进行说明,而不是将正确顺序写在某个构造函数里,应当着重注意声明顺序,避免不必要的错误。
在这里插入图片描述

初始化定义的顺序是先a后b,但编译器实际上是按照声明顺序先b后a的,所以b(a)先被初始化为随机值,随后a才被初始化为1;

派生类构造析构顺序

  • 派生类构造函数的调用顺序是按照继承的层次自顶向下、从基类再到派生类的

  • 类的构造,析构函数不能直接被继承,因此派生类构造函数总是 先 调用其直接基类构造函数 再 执行其他代码(其他代码遵循普通函数声明顺序进行构造);

(间接基类不能直接去调用,比如C继承B,B继承A,如果C(调用间接基类A)和B(调用直接基类A)的构造,那就是无意义的构造两次,浪费资源毫无益处)

#includeusing namespace std;//多继承场景
class A//定义基类A
{
public:A()  { cout << "调用构造函数 A()" << endl; } ~A() { cout << "调用析构函数 ~A()" << endl; }
};
class B : public A
{
public:B() { cout << "调用构造函数 B()" << endl; } ~B() { cout << "调用析构函数 ~B()" << endl; }
};
class C : public B 
{
public:C()  { cout << "调用构造函数 C()" << endl; }~C() { cout << "调用析构函数 ~C()" << endl; }
};int main()
{C c;return 0;
}

运行结果:

在这里插入图片描述

解析

根据调试可以发现:

编译器为了初始化C,先调用了C的构造函数,紧接着C在定义其他变量前,有继承关系,则C的构造函数自动调用其直接基类B的构造函数,B在完成其构造函数前,有继承关系则B的构造函数又自动调用其直接基类A的构造函数;

那么最先完成构造函数的是最顶层A类,紧接着B类完成基类A的初始化,B类的构造也进一步完成,到了最底层C类,直接基类B和间接基类A(B也把他的构造调用了)都构造好了,然后才是C也完成了他的构造函数; (如果有其他变量,则在上述继承顺序构造的基础上,再加上遵循普通函数的声明顺序进行构造;)

析构顺序类似于压栈和弹栈,刚好相反;

扩展菱形多继承场景

//菱形 多继承场景
class A//定义基类A
{
public:A() { cout << "调用构造函数 A()" << endl; }~A() { cout << "调用析构函数 ~A()" << endl; }
};
class B : public A
{
public:B() { cout << "调用构造函数 B()" << endl; }~B() { cout << "调用析构函数 ~B()" << endl; }
};
class C : public A
{
public:C() { cout << "调用构造函数 C()" << endl; }~C() { cout << "调用析构函数 ~C()" << endl; }
};
class D : public B,public C
{
public:D() { cout << "调用构造函数 D()" << endl; }~D() { cout << "调用析构函数 ~D()" << endl; }
};int main()
{D d;return 0;
}

那么执行结果是:

在这里插入图片描述

显然能看到,出现看了菱形继承 数据二义性的问题!

含虚基类的派生类构造析构顺序

class A//定义基类A
{
public:A() {cout<<"调用A构造函数"<
public:B() :A() {cout<<"调用B构造函数"<
public:C() :A() ,B(){cout<<"调用C构造函数"<C c;return 0;
}

运行结果:

在这里插入图片描述

虽然运行结果类似,但通过调试可以看到,与上面普通的派生类调用构造规则不同!

这次编译器直接从C类的构造函数中,先执行了A类的构造函数再执行了B类的构造函数!显然,C不但执行了直接基类A的构造,也执行了间接基类B的构造;

解析

如果出现虚继承,某个类型含有虚基类时,最后的派生类中不仅要负责对其直接基类进行初始化,还要负责对直接基类的虚基类初始化

C++编译系统只执行最后的派生类对虚基类的构造函数的调用,而忽略虚基类A的其他派生类(如类B)
对虚基类A的构造函数的调用,这就保证了虚基类的数据成员不会被多次初始化。

扩展菱形多继承场景(引入虚继承)

#includeusing namespace std;//虚继承,菱形继承
class A//定义基类A
{
public:A() { cout << "调用A构造函数" << endl; }
};
class B : virtual public A
{
public:B() :A() { cout << "调用B构造函数" << endl; }
};
class C :virtual public A 
{
public:C() :A() { cout << "调用C构造函数" << endl; }
};
class D : public B,public C 
{
public:D() :A(),B(),C() { cout << "调用D构造函数" << endl; }
};int main()
{D d;return 0;
}

在这里插入图片描述

(这不就是菱形继承解决了重复初始化造成的数据二义性的原理嘛~!)

相关内容

热门资讯

喜欢穿一身黑的男生性格(喜欢穿... 今天百科达人给各位分享喜欢穿一身黑的男生性格的知识,其中也会对喜欢穿一身黑衣服的男人人好相处吗进行解...
发春是什么意思(思春和发春是什... 本篇文章极速百科给大家谈谈发春是什么意思,以及思春和发春是什么意思对应的知识点,希望对各位有所帮助,...
网络用语zl是什么意思(zl是... 今天给各位分享网络用语zl是什么意思的知识,其中也会对zl是啥意思是什么网络用语进行解释,如果能碰巧...
为什么酷狗音乐自己唱的歌不能下... 本篇文章极速百科小编给大家谈谈为什么酷狗音乐自己唱的歌不能下载到本地?,以及为什么酷狗下载的歌曲不是...
华为下载未安装的文件去哪找(华... 今天百科达人给各位分享华为下载未安装的文件去哪找的知识,其中也会对华为下载未安装的文件去哪找到进行解...
怎么往应用助手里添加应用(应用... 今天百科达人给各位分享怎么往应用助手里添加应用的知识,其中也会对应用助手怎么添加微信进行解释,如果能...
家里可以做假山养金鱼吗(假山能... 今天百科达人给各位分享家里可以做假山养金鱼吗的知识,其中也会对假山能放鱼缸里吗进行解释,如果能碰巧解...
四分五裂是什么生肖什么动物(四... 本篇文章极速百科小编给大家谈谈四分五裂是什么生肖什么动物,以及四分五裂打一生肖是什么对应的知识点,希...
一帆风顺二龙腾飞三阳开泰祝福语... 本篇文章极速百科给大家谈谈一帆风顺二龙腾飞三阳开泰祝福语,以及一帆风顺二龙腾飞三阳开泰祝福语结婚对应...
美团联名卡审核成功待激活(美团... 今天百科达人给各位分享美团联名卡审核成功待激活的知识,其中也会对美团联名卡审核未通过进行解释,如果能...