1.引言
曾今见过一道面试题:构造函数可以调用虚函数吗?语法上通过吗?语义上可以通过吗?
对于这道题的回答首先需要了解vptr初始化语义学。
2.vptr初始化语义
分析一下这段程序:
#include <iostream>
using namespace std;
class Point
{
public:
Point(floatx=0.0, float y=0.0):_x(x),_y(y){size();};
Point(constPoint&);
Point&operator=(const Point&);
virtual~Point(){}
virtualvoid size(){print();cout << "Point size: " <<sizeof(Point)<<endl;}
virtualvoid print(){cout << "This is Point. ";}
protected:
float_x, _y;
};
class Point3d:virtual public Point
{
public:
Point3d(floatx=0.0, float y=0.0, float z=0.0)
:Point(x,y),_z(z){size();}
Point3d(constPoint3d& rhs)
:Point(rhs),_z(rhs._z){}
virtual~Point3d(){}
Point3d&operator=(const Point3d&);
virtualvoid size(){print();cout << "Point3d size: " <<sizeof(Point3d)<<endl;}
voidprint(){cout << "This is Point3d. ";}
protected:
float_z;
};
class Vertex:public virtual Point
{
public:
Vertex(floatx=0.0, float y=0.0, float t=0.0)
:Point(x,y),_t(t){size();}
Vertex(constVertex& rhs)
:Point(rhs),_t(rhs._t){}
virtual~Vertex(){}
Vertex&operator=(const Vertex&);
virtualvoid size(){print();cout << "Vertex size: " <<sizeof(Vertex)<<endl;}
voidprint(){cout << "This is Vertex. ";}
protected:
float_t;
};
class Vertex3d:public Point3d,public Vertex
{
public:
Vertex3d(floatx=0.0, float y=0.0, float z=0.0)
:Point3d(x,y,z),Vertex(x,y,z){size();}
Vertex3d(constVertex3d& rhs)
:Point3d(rhs),Vertex(rhs){}
virtual~Vertex3d(){}
Vertex3d&operator=(const Vertex&);
virtualvoid size(){print();cout << "Vertex3d size: " << sizeof(Vertex3d)<<endl;}
voidprint(){cout << "This is Vertex3d. ";}
};
int main()
{
Vertex3dv3d;
return0;
}
运用VC++编译后的运行结果为:
这个结果证明,在各类的构造函数中调用虚函数,无论是直接调用还是,调用虚汗是之后再调用。那么都被决议为正在构造的对象调用的虚函数。
因为这些都是直接调用的并不是用一个基类指针指向派生类来调用虚函数。
现在对程序做一定的修改
①将Vertex3d的构造函数改为:
Vertex3d(float x=0.0, float y=0.0, floatz=0.0)
:Point3d(x,y,z),Vertex(x,y,z){Point*p= new Point;p->print();size();}
此时的输出是:
证明可以正常调用,也证明了各个构造函数的调用次序。
②现在改为:
Vertex3d(float x=0.0, float y=0.0, floatz=0.0)
:Point3d(x,y,z),Vertex(x,y,z){Point*p= new Point3d;p->print();size();}
此时的输出是:
证明在构造函数中,子类的虚机制可以调用。
③现在改为:
Vertex3d(float x=0.0, float y=0.0, floatz=0.0)
:Point3d(x,y,z),Vertex(x,y,z){Point*p= new Vertex3d;p->print();size();}
那么此时就是一个死循环!!!!!为什么?
3.结论
通过上述的实验就可以得出,vptr的初始时间是:在base class constructor 调用操作之后,但是在程序员提供的代码或是成员初始化列表中所列的成员初始化语句之前。
综合来说构造函数的顺序是:
1、 在派生类(最终对象)的构造函数中,所有的虚拟基类的构造函数和上一层的构造函数将会被调用。
2、 上述完成之后,对象的vptr被初始化,指向相关的virtualtable。
3、 如果有成员初始化列表的话,在构造函数体内扩展开来。
4、 最后执行程序员所提供的代码。
4.其他问题的思考
这个程序在VC++坏境是这样的输出结果,通过g++编译后,虽然关于本文所论述的vptr的初始化语义是没有变化的。但是在g++中输出结果是不一样的,区别在于各个类的大小。这与编译器记录虚拟继承的方式有关。VC++通过记录偏移量的方式来找到虚拟基类的位置。所以在类中偏移量占据一定字节。g++将偏移量记录在虚函数表中,在虚函数表中偏移量为正则存放的是虚函数地址;偏移量为负存放的是虚拟基类的偏移量。所以g++上虚拟继承下的类的大小比VC++编译的程序要小。
但是VC++具体怎么安排,搞不清楚。这方面的实验还没有做过。