Default Constructor的操作:在需要的时候编译器才会创建出来,并且只满足编译器的需要。
有四种情况会造成Default Constructor构造出来
1 带有Default Constructor的成员类对象
如果一个类没有任何contructor,但是它内含一个member object,并且该成员有默认构造函数,则编译器构造出来的隐式默认构造函数是有用的。不过合成操作也只有在构造函数真正需要被调用的时候才发生。
在不同的编译模块中,编译器避免合成多个default constructor的方案是:将四种类型的构造函数都以inline方式完成。一个inline函数有静态链接,不会被文件以外的看到。
如果class A内含一个或一个以上的member class objects,则在A的每一个构造函数中都必须调用每个类成员的默认构造函数。编译器会扩张已存在的构造函数,在其中安插一些代码,使得user code被执行之前,先调用必要的默认构造函数。调用各个类成员的构造函数的顺序是按照在类A 中的声明顺序。
2 带有默认构造函数的基类的类
如果一个没有任何构造函数的class派生子一个“带有默认构造函数的”基类,则这个继承类的构造函数是有用的,并要被合成出来。并且调用上一层基类的默认构造函数(按照声明顺序)。对于一个后继类来说,这个合成的构造函数与被显示提供出来的没有区别。
如果类有多个构造函数,则对于每一个构造函数,将调用所有必要的默认构造函数的程序代码加进去,但并不会合成新的默认构造函数。如果这个类同时包含带有默认构造函数的成员类对象,则这些也会被调用,但是在所以的基类之后。
3 带有一个虚函数的类
类声明(或继承)一个虚函数;或类派生子一个继承串链,其中有一个或更多的虚基类
含有虚函数的类,编译器有以下行动
(1)产生一个virtual function table(vtbl)来放置类的虚函数地址,(2)在每个类对象中,一个额外的指针成员(vptr)合成出来,指向这个虚函数的地址。并且对虚函数的调用都将转变成虚函数表中的地址。
4 带有一个虚基类的类
虚基类在每一个继承类对象的位置需要在执行器准备妥当,一个做法是在继承类对象的每一个虚基类中安插一个指针。所有使用引用或指针来存取一个虚基类的操作都可以通过这个指针完成。这个指针就是在构造函数期间初始完成。对于类的每一个构造函数,编译器会将存取虚基类的内容改写出来(用这个指针引用)
合成默认构造函数中,之后基类子对象,类成员会被初始化,基础类型不会被初始化,需要程序员自己初始化。
Copy constructor的构造操作:
只有nontrival的实例才会被合成拷贝构造函数,决定一个copy constructor是否是trivial(无用的)的标准是class是否展现出所谓的"bitwise copy semantics"(位逐次拷贝,这应该是按位拷贝就可以了,普通的类型都可以准确的完成)
不展现bitwise copy semantics的四种情况(与默认构造函数被合成的情况相同,都一一对应)
1 当类含有一个类成员而后者的类中有一个拷贝构造函数,无论是显示声明还是被合成出来的
2 当类继承一个基类,而这个基类有拷贝构造函数,也无论显示声明还是被合成
3 当类声明了一个或多个虚函数
4 当类派生子一个继承串链,而有一个或多个是虚基类时。
前两种,编译器将类成员或基类的拷贝构造函数安插到被合成的拷贝构造函数中。
后2种,在同一个类的对象也是以该类的对象初始化时也是安全的,位逐次拷贝可以满足。
但是在当一个基类对象被赋值为子类对象,这时子类对象被切割,但是不满足bitwise copy semantics了。
3 的原因是基类和子类都有虚函数表,而且不一样,并且对象中含有指向这个表的指针。如果直接将子类的这个指针拷贝到这个父类的对象,则父类的这个指针将指向子类的虚函数表了!!这并不是我们想要的,所以必须由编译器来完成这个vptr的重新赋值操作。
但是如果是基类指针或引用,则由于多态,还是指向子类的,位拷贝是满足的。
4 的不成立之一也是当一个对象用它的子类初始化时,因为它们还有指向对象中虚基类部分的指针,而这个指针分别指向各自的部分,不能直接拷贝,所以需要编译器动态调整。所以不满足位拷贝语义,
还有一种情况是如果一个对象被赋值为一个声明为该对象的指针取内容时,编译器无法判断是否还保持着位语义,
Raccoon *ptr; Raccoon little_critter = *ptr; //无法判断