深入探索C++对象模型(5)
虚拟继承下的对象构造:
由于虚拟基类对象在子类中只能保持一个实例,那么,子类构造的时候调用父类的构造函数的时候必须保证虚拟基类对象不能够重复构造。那么,C++规定虚拟基类对象的构造只能是最外层的子类进行构造,浅层次的子类将不会在进行构造,保证了虚拟基类对象的唯一性。
在虚拟继承体系下,子类的构造函数中必须做一个判断,设置一个标准位,用来判断虚拟基类对象是否已经构建,然后将该标志为传递给浅层次的子类,那么虚拟基类将不会再次构造。例如,编译器会为子类构造函数内部设置标志位
Point3D::Point3D(Point3D *this,bool _most_derived)
{
if(_most_derived!=fales)Point();//如果是最外层子类,构建虚拟基类对象
Point2D(false);
Vertex(false);//将false传入说明其父类不是最外层,将不会构建虚拟基类对象
}
继承体系下的对象构造:
必须首先将父类对象构造再构造子类对象内容。在子类构造函数中调用父类构造函数的方法可以是在成员初始化列表中显示调用构造函数,如果没有在成员初始化列表中进行构造,那么,编译器会在子类构造函数中扩充调用父类默认构造函数进行构造父类对象。
------------------------------------------------------------------------------------------------------------------------------
Vptr的深入探索
在前面我们知道,Vptr必须在构造对象的时候进行初始化设置,使它指向正确的类的虚表地址。那么,在什么时候进行vptr的设置呢?
C++标准规定构造函数中内容执行的顺序:
1、首先调用虚拟基类(若有首先调用虚拟基类构造函数)或基类们的构造函数,构建基类子对象;
2、设置该对象的vptr,使其指向适应的虚拟函数表;
3、执行成员初始化列表中的成员初始化;
4、执行用户程序内容。
因此,当遇到在构造函数或析构函数中调用虚函数问题的时候,答案就会很明确了。
在基类构造函数中调用虚函数,将不会使用多态机制,即不会调用其子类中的虚函数,因为在基类构造的时候,vptr的设置仍指向基类的虚表,而子类还未完成构造,vptr还未指向子类的虚表,因此,此时不会使用多态机制,仍然调用基类中的虚函数实例。而在构造函数中使用成员函数,成员函数中调用虚函数时也不适用多态机制。只有在非构造函数中调用虚函数时才会使用多态机制。
同理,析构函数中内容的顺序正好相反:
1、调用子类析构函数中实体,完成用户程序中内容的退栈;
2、析构释放子类中不同于基类的成员;
3、调整设置vptr,使其指向基类相对应的虚表;
4、完成基类的析构。
在基类析构函数中调用虚函数,也不会实现多态机制,因为子类已经析构完毕,vptr指向基类的虚表。
---------------------------------------------------------------------------------------------------------------------------------
赋值函数的深入探索:
未显示定义的赋值函数,编译器将视情况为类合成赋值函数,条件和合成复制构造函数的相同,只有当复制不适合bitmise的时候才会很成默认构造函数。
注意:复制构造函数时进行vptr的设定,而赋值函数不会进行vptr的设置,也就是说当以子类对象赋值给父类对象时,将不会改变父类对象的vptr指向,因为父类对象在构造的嘶吼已经进行了设定。
赋值函数需要进行自我识别:
加上一句,防止自我复制
if(this==&参数对象)return *this;
另外,赋值函数不能使用成员初始化列表,只有构造函数才能使用,这样就会导致,虚拟继承情况下,使用赋值函数复制对象时,会在被赋值的对象中出现多个虚拟基类对象的现象。
例如:
类A,B虚拟继承类base,C继承A和B,那么C的赋值函数就会这样写:
C& operator=(const C& c)
{
if(this==&c)return *this;
A::operator=(c);
B::operator=(c);//导致出现两份虚拟基类对象实例
//C自己的成员的复制
return *this;
}
建议:尽量不要使用赋值函数进行虚拟继承子类对象的复制。