C++对象模型:g++的实现(七)
这篇博客来总结一下《深度探索C++对象模型》第5章构造、析构、拷贝语义学的内容。
是对主要内容的总结,原文请看原书。
1. 构造函数
按照发生的顺序,一个类的构造函数会做的事情:
- 所有虚基类的构造函数会被调用,从左到右,从深到浅:
- 如果虚基类被列在member initialization list(成员初始化列表)中,那么如果有任何明确指定的参数,都应该传递过去;如果没有列在list中,而该类有default constructor,也应该调用;
- 此外,类中每一个virtual base class subobject(虚基类子对象,此处我写为“子对象”意为其是该类的一部分)的偏移量(offset)必须在执行期可被存取(也就是虚表指针必须被设置好,其中有虚基类偏移量);
- 如果class object是最底层的(most-derived,也就是最后的派生类),其constructor可能被调用,某些用以支持这个行为的机制必须被放进来。
- 所有上一层的base class constructor必须被调用,以base class的声明顺序为顺序,跟其在member iniaialization list中的顺序无关;
- 如果base class被列于member initialization list中,那么任何明确指定的参数都应该被传递过去;
- 如果base class没有被列于member initialization list中,而他有default constructor,那么就调用(书中还提到或者有default memberwise copy constructor就调用,没有参数为什么调用拷贝构造函数?我认为可能是一个错误);
- 如果base class是第二或后继的base class,那么this指针需要被调整。
- 如果class object有virtual table pointer(s)(虚表指针),它们必须被设定初值,指向适当的virtual table;
- 记录在member initialization list(成员初始化列表)中的data member(数据成员,即非静态成员变量)初始化操作会被放进构造函数本身,并以成员的声明顺序为顺序(进行初始化);
- 如果某个成员变量没有出现在member initialization list中,但在声明时被赋予了初值,则设置此初值(或调用构造函数)【这是我自己加的,C++11之前不允许在声明中设置初值】;
- 如果有一个成员变量没有出现在member initialization list中,但他有default constructor,那么会调用它;
- 构造函数中用户提供的语句被执行。
这个顺序给了我们以一个启发:那就是类的构造是从深到浅的,从内到外构造的过程就是该类从基类到派生类的提升过程。所以在构造函数中尽量不要使用虚函数,因为此时类的构造可能还未结束(如果此类被一个派生类继承,此时虚函数指针还未被正确设置),此时调用虚函数最多会调用自己定义的的虚函数和自己子类的虚函数,而外层可能的派生类定义的虚函数则不会被调用。
2. 对象复制语义学
一个class对于默认的copy assignment operator,在以下情况不会表现出bitwise copy(按位拷贝,就是直接拷贝)语义:
- 当class内带一个member object,其class有一个copy assignment operator时;
- 当一个class的base class有一个copy assignment operator时;
- 当一个类声明了任何virtual function时(我们一定不能拷贝右端class object的vptr,因为它可能是一个derived class object(派生类对象));
- 当类继承自一个virtual base class (不论此base class有没有copy operator,因为虚继承也意味着一定有vptr)。
3. 解构语义学
按照发生的顺序,一个类的析构函数会做的事情:
- 如果一个object内带有vptr,那么首先重设相关的vptr;
- destructor的函数本身现在被执行,也就是说vptr会在用户代码执行前被重设(reset);
- 如果class拥有member class objects,而后者有构造函数,那么它们会以其声明的顺序的相反顺序被调用;
- 如果有任何直接的(上一层)nonvirtual base class拥有destructor,那么它们会以其声明顺序的相反顺序被调用;
- 如果有任何virtual base class 拥有destructor,而当前讨论的这个class是最尾端(most-derived)的class,那么它们会以原来的构造顺序的相反顺序被调用。
在书中作者说析构函数顺序是构造函数顺序的相反顺序,因此译者认为作者写错了,顺序是:2, 3, 1, 4, 5。即vptr的设置放在子类析构之后。但是其实上面12345的顺序是对的。
从其流程来看,和构造函数一样,我们也不应该在析构函数里调用虚函数,因为此时vptr被设置为本类的vptr,而不是可能的派生类的vptr。
其他比如纯虚函数就不说了,我觉得用处好像不是很大。