The Semantics of Constructors——2.1 默认构造函数的构造操作
The Semantics of Constructors
2.1 Default Constructor的构造操作
C++新手一般有两个常见的误解:
-
任何 class如果没有定义默认构造函数,就会被合成出一个来。
-
编译器合成出来的默认构造函数会显式设定“类内每一个数据成员的默认值”。
2.1.1 什么是默认构造函数?
https://www.ibm.com/docs/en/zos/2.2.0?topic=only-default-constructors-c
A default constructor is a constructor that either has no parameters, or if it has parameters, all the parameters have default values.
默认构造函数要么没有参数,要么所有参数都有默认值。
2.1.2 trivial(没有用的) constructor
- 对于 class X,如果没有任何用户声明的构造函数,那么会有一个默认构造函数被隐式声明出来,来满足编译器的需要……一个被隐式声明出来的默认构造函数并不会执行什么特别的操作,也就是无用的(trivial)。
The constructor will have no constructor initializer and a null body.
-
在合成的默认构造函数中,只有
基类子对象
和类对象数据成员
会被初始化。所有其他的非静态数据成员
(如整数、整数指针、整数数组等等)都不会被初始化。这些初始化操作对程序而言或许有需要,但对编译器则非必要。 -
如果程序需要一个“把某指针设为0”的默认构造函数,那么应该由程序员来完成。
什么时候一个类的构造函数是trivial?
A constructor of a class A is trivial if all the following are true:
- It is implicitly declared or explicitly defaulted.
- A has no virtual functions and no virtual base classes
- All the direct base classes of A have trivial constructors
- The classes of all the nonstatic data members of A have trivial constructors
但以下四种情况,类的默认构造函数是有用的(non-trivial)。
2.1.3 nontrivial(有用的) constructor
我理解的是这里的“带有默认构造函数”指的是具有nontrivial构造函数,编译器合成或者设计者显示声明。
(1)类的类对象数据成员“带有默认构造函数”
-
如果一个类没有任何构造函数,但它内含一个具有nontrivial默认构造函数的成员对象,那么这个类的隐式默认构造就是“nontrivial”,编译器需要为该类合成出一个默认构造函数。不过这个合成操作只有在构造函数真正需要被调用时才会发生。 当声明了一个这个类的对象,编译器就会合成构造函数,并且以inline方式完成。
-
如果有多个成员类对象都要求构造函数初始化操作,将如何?C++语言要求以“类对象成员在类中的声明顺序”来调用各个构造函数。这一点由编译器完成,它为每一个构造函数安插程序代码,以“成员声明顺序”调用每一个成员所关联的默认构造函数。这些代码将被安插在显示的用户代码之前。
(2) 类的基类“含有默认构造函数”
-
如果一个没有任何构造函数的类派生自一个“带有默认构造函数”的基类,那么这个派生类的默认构造函数会被视为nontrivial,并因此需要被合成出来。
-
如果类的设计者提供了多个构造函数,而没有默认构造函数,那么编译器将会使用基类的默认构造函数扩张这个派生类的构造函数,不会为这个类合成默认构造函数。
-
如果一个类继承了其它基类,同时含有类对象数据成员,那么这个为这个类合成的构造函数,会先调用基类的构造函数,再调用成员类的构造函数。
(3)“带有虚函数”的类
有两种情况,也需要合成默认构造函数:
-
类声明(或继承)一个虚函数。
-
class派生自一个继承串链,其中有一个或更多的虚基类。
对于那些未声明任何构造函数的类,编译器会为它们合成一个默认构造函数,以便正确地初始化每一个类对象的虚函数表指针(vptr)。下面两个扩张行动会在编译期间发生:
-
一个 虚函数表(在 cfront中被称为 vtbl)会被编译器产生出来,存放类的虚函数地址。
-
在每一个类对象中,一个额外的指针成员(也就是vptr)会被编译器合成出来,内含相关的类虚函数表(vtbl)的地址。
(4)"带有虚基类"的类
菱形继承、虚继承的内存分布
https://blog.csdn.net/AgoniAngel/article/details/105893798
编译器无法固定住foo()之中“经由pa而存取的X::i”的实际偏移位置,因为pa的真正类型可以改变。编译器必须改变“执行存取操作”的那些代码,使X::i可以延迟至执行期才决定下来。
对于类所定义的每一个构造函数,编译器会安插那些“允许每一个虚基类的执行期存取操作”的代码。如果类没有声明任何构造函数,编译器必须为它合成一个默认构造函数。
//可能的编译器转变操作
void foo(const A* pa) { pa->__vbcX-i = 1024;}
其中__vbcX表示编译器所产生的指针,指向virtual base class X。