一 前提,关于C++编译器给我们生成 默认构造函数 的错误认知
传统知识认为:如果在一个类中,我们没有定义任何的构造函数,那么编译器会为我们隐式自动定义一个默认的构造函数,我们称这种构造函数为 "合成的默认构造函数"
实际上这个结论是错误的,正确的说法是:如果在一个类中,我们没有定义任何的构造函数,那么编译器 会在必要的时候 为我们隐式自动定义一个默认的构造函数,我们称这种构造函数为 "合成的默认构造函数"。
也就是说:在类没有构造函数的情况下,有可能 C++编辑器 会生成 "合成的默认构造函数",有可能不会合成
二 那么什么是必要的时候呢?以及怎么验证这个问题呢?
2.1 如何验证这个问题?使用工具分析对应的.obj或者.o文件
每个.cpp文件都会编译生成一个.obj文件,或者一个.o文件
windows : vs2017生成.obj文件
linux : linux生成.o文件
我们可以通过工具分析这个.obj文件,检查是否有构造函数的生成
工具在哪里?
windows: vs2017中的 "VS 2017的开发人员命令提示符 "
linux:
文件在哪里 ?即 .obj 或者 .o文件的位置?
vs2017 : 在debug目录下
如何使用?使用dumpbin命令分析 .obj文件,将分析出来的内容写到一个txt文件中,方便分析
打开 "VS 2017的开发人员命令提示符 "
cd 到.obj文件目录下
dempbin/all teacher100.obj > my.txt
D:\AllInformation\cpp\001for\001for\ConsoleApplication1\x64\Debug>dumpbin /all ConsoleApplication1.obj > my.txt
然后在 my.txt中 去查找 关键字 Teacher11::Teacher11,,就会发现在obj中确实没有构造函数的
好了现在我们工具有了,方法有了,写一个case验证一下。
2.2 结论1:如果该类Teacher11 没有任何构造函数,但是包含了一个其他类的成员变量Teacher10,而且包含的这个对象类型是有 明确的构造函数的。这时候,编译器会给我们自动生成默认的构造函数,编译器自动合成这个默认的构造函数的目的是:在这个自动生成的默认的构造函数中,会调用Teacher10的构造函数,以保证Teacher10可以被初始化。如果包含了多了其他类成员,例如有Teacher10,Teacher9,Teacher8,那么这个调用顺序为 在Teacher11中定义的顺序。
class Teacher10 {
public:Teacher10() {cout << "teacher10 的构造函数被调用"<<endl;}
};class Teacher11 {Teacher10 tea10;
};void main() {Teacher10 tea10;Teacher11 tea11;cout << "start " << endl;
}
2.3 结论2:如果该类Teacher14 没有任何构造函数,但是它的爸爸Teacher12明确的构造函数的。这时候,编译器会给我们自动生成默认的构造函数,编译器自动合成这个默认的构造函数的目的是:在这个自动生成的默认的构造函数中,会调用它的父类Teacher12的构造函数,以保证Teacher12可以被初始化。
如果多继承,即Teacher14,继承了 Teacher12,也继承了Teacher13,也是那个爸爸有明确的构造函数,生成的默认构造函数会调用 爸爸的构造函数,不会给没有明确构造函数的爸爸生成默认构造函数。也不会调用没有明确构造函数的爸爸
class Teacher12 {
public:Teacher12() {}
};
class Teacher13 {};class Teacher14 :public Teacher12{//会生成14的默认构造函数,不会生成13的
//class Teacher14 :public Teacher12, public Teacher13 {//会生成14的默认构造函数,不会生成Teacher13的构造函数
public:};void main() {Teacher14 tea14;Teacher12 tea12;Teacher13 tea13;
}
2.4 结论3:如果一个类中含有虚函数,但是没有任何构造函数。编译器会给我们生成一个默认的构造函数。编译器会给我们生成一个基于该类的虚函数表 vftable.
那么这个构造函数又干了些什么呢?将虚函数表的指针和类对象关联起来。
如下的解释,如果看不懂,没关系,后续在虚函数的整理学习中---详细解释。这里只要知道上面的结论就行。
//因为虚函数的存在,编译器会给我们生成一个基于该类的虚函数表 vftable,这个虚函数表有很多项,每一项都记录着每一个虚函数的地址。
//在这个构造函数中,会把类的虚函数表地址赋给类对象的 虚函数表指针(赋值语句 )。
//我们可以把 虚函数指针,看成是我们表面上看不见的一个类的成员变量。因此
//虚函数是跟着类走的。
//为什么这么麻烦,因为虚函数的调用存在一个多态问题
如果我们有虚函数,也有显示的构造函数呢?编辑器会给我们这个显示的构造函数中,添加代码,添加的代码也不难想象;会给我们生成虚函数表,将虚函数表的地址 赋给 对象的虚函数表指针。
2.5 结论4:如果一个类带有虚基类,编译器也会为他合成一个默认的构造函数。可以不关注这个,写在这里,只是为了知识的完整性,实际工作中,谁要搞这个,会被code review致死。
虚基类:通过两个直接基类继承同一个基类。所以一般有三层,有爷爷Grand,有两个爹A,A2,有孙子C