5.1“无继承”情况下的对象构造
考虑下面这个程序片段:
(1) Point global;
(2)
(3) Point foobar()
(4) {
(5) Point local;
(6) Point *heap = new Point;
(7) *heap = local;
(8) // ... stuff ...
(9) delete heap;
(10) return local;
(11) }
L1、L5、L6表现出三种不同的对象产生方式:global内存配置、local内存配置和heap内存配置。L7把一个class object指定给另一个,L10设定返回值,L9则显式地以delete运算符删除heap object。
一个object的生命,是该object的一个执行期属性。local object的生命从L5的定义开始,到L10为止。global object的生命和整个整个程序的生命相同。heap object的生命从它被new运算符配置出来开始,到它被delete运算符摧毁为止。
如果Point声明为以下,则是一种Plain OI’ Data声明形式:
typedef struct
{
float x,y,z;
} Point;
编译器会为Point声明无用的构造、析构、拷贝构造、赋值拷贝构造,事实上那些trival members要不是没被定义,就是没被调用。
foobar()函数中有L5,有一个Point object local,同样也是既没有被构造也没有被析构。当然,Point object local如果没有先经过初始化,可能会成为一个潜在的程序“bug”,万一第一次使用它就需要其初始值的话(像L7)。
(6) Point *heap = new Point;
会被转换为对new运算符(由libeary提供)的调用:
Point *heap = _new(sizeof(Point));
最后,函数以传值(by value)的方式将local当做返回值传回,这在观念上会触发trivial copy constructor,不过实际上return操作只是一个简单的位拷贝操作,因为对象是个Plain OI’ Data。
5.2 继承体系下的对象构造
- 所有virtual base class constructors必须被调用,从左到右,从最深到最浅。
在更深层的继承情况下,构造函数会给virtual base class的子类构造函数传入一个参数,抑制了子类调用父类的构造函数,这样在虚拟继承的情况下,就只会调用一次,virtual base class的构造函数。 - 所有上一层的base class constructors 必须被调用,以base class 的声明顺序为顺序(与member initialization list中的顺序没关联)
- 如果class object有virtual table pointer(s),它(们)必须被设定初值,指向适当的virtual table(s)。
- 记录在member initialization list中的 data members 初始化操作会被放进 constructor 的函数本体,并以members声明顺序为顺序。
如果有一个member并没有出现在member initialization list之中,但它有一个default constructor,那么该default constructor必须被调用。
注:在一个由程序员供应的copy operator中忘记检测自我指派(赋值)操作是否失败,是新手容易犯的错误。
vptr初始化操作应该如何处理?
在base class constructors 调用操作之后,但是在程序员供应的代码或是“member initialization list 中所列的 members 初始化操作”之前。
- 在derived class constructor 中,“所有virtual base classes”及“上一层 base class”的 constructors 会被调用。
- 上述完成之后,对象的vptr(s)被初始化,指向相关的 virtual table(s)。
- 如果有 member initialization llist 的话,将在 constructor 体内扩展开来。这必须在 vptr 被设定之后才做,以免有一个virtual member function 被调用。
- 最后,执行程序员锁提供的代码。
所以讲构造函数设置为虚函数并没有卵用,因为只有构造函数执行到一半,虚函数指针才被构造出来,所以构造函数先于虚函数构造表。
析构语意学
一句话,按照构造这个对象的相反顺序来调用。