深入探索C++对象模型(七) 构造

三个重要函数:构造函数,析构函数,拷贝构造函数。

1. 无继承情况下的对象构造。

当类中存在虚函数时,编译器会对该类产生膨胀作用, 例如如下类:

[cpp]  view plain  copy
  1. class Point {  
  2. public:  
  3.     Point(float x = 0.0, float y = 0.0)  
  4.         : _x(x), _y(y){}  
  5.     virtual float z();  
  6. protected:  
  7.     float _x, _y;  
  8. };  

a. 我们所定义的构造函数中,会被附加一些代码,一边初始化虚表指针(vptr),这些代码会安插在任何基类构造函数的调用之后,但必须在任何使用者供应的代码之前,一种可能的上述Point类的构造函数扩展:

[cpp]  view plain  copy
  1. Point* Point::Point (Point* thisfloat x, float y)  
  2.                     : _x(x), _y(y)  
  3. {  
  4.     //设定虚表指针  
  5.     this->__vptr_Point = __vtbl__Point;  
  6.       
  7.     //扩展成员变量初始化列表  
  8.     this->_x = x;  
  9.     this->_y = y;  
  10.       
  11.     //传回this对象  
  12.     return this;  
  13. }  

b. 合成一个拷贝构造和拷贝赋值函数,这两个函数是重要的,会被调用的,用来在这两个函数被调用时,设定虚表指针等操作,可能的扩展结果:

[cpp]  view plain  copy
  1. //copy constructor  
  2. inline Point* Point::Point (Point* thisconst Point& rhs)  
  3. {  
  4.     //设定虚表指针  
  5.     this->__vptr_Point = __vtbl__Point;  
  6.   
  7.     //数据成员拷贝      
  8.       
  9.     //传回this对象  
  10.     return this;  
  11. }  

2. 继承情况下的对象构造。

当我们定义一个类对象时:T object,构造函数可能会发生扩展:

a. 初始化列表中数据成员初始化操作会放入构造函数本身,注意是以数据成员在类中的声明顺序,而非在初始化类表中的顺序。

b. 如果有某一个数据成员未出现在初始化列表中,但是他有默认构造函数,则该默认构造函数会被调用。

c. 在那之前,如果如果类该对象有虚表指针,它(们)会设定初值,指向适当的虚表(们)。

d. 在那之前,所有基类构造函数会被调用,按照它们在子类中的继承顺序:

    i. 如果基类在初始化列表中,任何明确指定的参数都需要传递进去。

    ii. 如果基类没有列于初始化列表,那么会调用可能存在的默认构造函数。

    iii. 如果基类是多重继承下的第二个或者后继,那么此时子类的this指针需要被调整,以指向该基类部分(subobject)。

e. 在那之前,所有虚基类的构造函数必须被调用, 从左到右,从最深到最浅。

    i. 如果基类在初始化列表中,任何明确指定的参数都需要传递进去,如果没有,会调用可能存在的默认构造函数。

    ii. 类中每一个虚基类子对象的偏移(offset)必须在执行期可被存取。

    iii. 如果类对象是最底层的(most-derived),其构造函数可能会被调用,某些支持这个行为的机制会被放入(下文提到的__most_derived)。

继续以Point为例,进行一下扩充,并声明Line类:


第二个构造函数会被扩展为:


析构函数会被扩展为:


虚拟继承情况下,子类构造函数的扩展有些不一样,考虑如下类以及其继承体系:


由于虚基类在子类中只有一份,所以虚基类Point的构造函数只能被调用一次,例如,在实例化Point3d或者Vertex对象时,作为二者直接基类的Point的构造函数需要调用,但是在定义Vertex3d对象时,会调用Point3d和Vertex的构造函数,此时二者的构造不应该调用Point的构造函数,因为Point已经在Vertex3d构造函数中调用过了,为了保证虚基类的唯一性,不允许再调用,也就是只有最下层的子类构造函数中需要调用虚基类的构造函数,Point3d的构造函数以及Vertex3d的构造函数的可能扩展如下,注意红框中的内容:


在Vertex3d构造函数中,当调用其父类Point3d以及Vertex的构造函数时,__most_derived参数会设定为false,已压制而这对于Point的再次初始化:


3. 关于虚表指针的初始化在子类构造函数中的情况。

a. 子类构造函数中,所有虚基类以及基类的构造函数会被调用。

b. 接着是对象的虚表指针被初始化,指向相关的虚表。

c. 在构造函数内展开可能存在的数据成员初始化列表,由于此时可能有虚函数调用,所以该步必须在虚表指针初始化妥当之后。

d. 最后,指向我们码农提供的代码。

例如,之前例子中的PVertex类的某个构造函数如下:


可能会被扩展成为,下图貌似有一点遗漏,this->Vertex3d::Vertex3d(x, y, z)应该是:this->Vertex3d::Vertex3d(false, x, y, z)以防止虚基类多次构造。




猜你喜欢

转载自blog.csdn.net/coolwriter/article/details/80558086