16、继承体系下的对象构造

目录

1、虚拟继承

2、Vptr初始化语意学


当我们定义一个object如下:

T object;

在这里,不明显的是constructor的调用真正伴随了什么?Constructor可能含有大量的隐藏代码,因为每个编译器会扩充每一个constructor,扩充的程度视class T的继承体系而定。

这一节中,我要从“C++语言对classes所保证的语意”这个角度,探讨constructor扩充的必要性,如下所示的代码。

class Point
{
public:
	Point(float x = 0.0, float y = 0.0);
	Point(const Point&);             //译注:copy constructor
	Point& operator=(const Point& ); //译注:copy assignment operator
	
	virtual ~Point();
	virtual float z() { return 0.0; }
	//...
protected:
	float _x,_y;
};


class Line
{
	Point _begin,_end;
public:
	Line(float=0.0, float=0.0, float=0.0, float=0.0);
	Line(const Point&, const Point&);
	
	draw();
	//...
};

每一个explicit constructor都会被扩充以调用其两个member class objects的constructors。如果我们定义constructor如下:

Line::Line(const Point&begin, const Point &end)
	:_end(end),_begin(begin)
{}

它会被编译器扩充并转换为:

Line *
Line::Line(Line *this,
	const Point&begin, const Point &end)
{
	this->_begin.Point::Point(begin);
	this->_end.Point::Point(end);
	return this;
}

当程序员写下:Line a;

Implicit Line destructor会被合成出来,其中它的member class objects的destructors会被调用(以其构造的相反顺序):

//C++伪代码:合成出来的Line destructor
inline void
Line::~Line(Line *this)
{
	this->_end.Point::~Point();
	this->_begin.Point::~Point();
}

类似的道理,当程序员写下:Line b = a;

时,implicit Line copy constructor operator 会被合成出来,成为一个inline public member。

1、虚拟继承

考虑下面这个虚拟继承。

class Point3d : public virtual Point{
public:
	Point3d(float x=0.0,float y=0.0,float z=0.0)
		:Point(x,y),_z(z){}
	Point3d(const Point3d &rhs)
		:Point(rhs),_z(rhs.z){}
	~Point3d();
	Point3d& operator=(const Point3d&);
	
	virtual float z(){return _z;}
	//...
protected:
	float _z;
};

传统的“constructor扩充现象”并没有用,这是因为virtual base class的“共享性”之故:

//C++伪代码
//不合法的constructor扩充内容
Point3d*
Point3d::Point3d(Point3d *this,
	float x,float y,float z)
{
	this->Point::Point(x,y);
	this->__vptr_Point3d = __vtbl_Point3d;
	this->__vptr_Point3d_Point = __vtbl_Point3d_Point;
	this->_z = rhs._z;
	return this;
}

上面的扩充有啥问题么?

试想以下的三种类的派生情况:

class Vertex : virtual public Point {...};
class Vertex3d : public Point3d,public Vertex {...};
class PVertex : public Vertex3d {...};

Vertex的constructor必须也调用Point的constructor。然而,当Point3d和Vertex同为Vertex3d的subobjects时,它们对Point constructor的调用操作一定不可以发生;取而代之的是,作为一个最底层的class,Vertex3d有责任将Point3d初始化。而更加往下的继承,则由PVertex来负责完成“被共享之Point subobject”的构造。

那么以上的问题怎么解决呢?constructor的函数体因而必须条件式地测试传进入的参数,然后决定调用或者不调用相关的virtual base class constructors。下面是Point3d的constructor扩充内容。

//C++伪代码
//在virtual base class情况下的constructor扩充内容
Point3d *
Point3d::Point3d(Point3d *this,bool __most_derived,
			float x,float y,float z)
{
	if(__most_derived != false)
	{
		this->Point::Point(x,y);
	}
	this->__vptr_Point3d = __vptr_Point3d;
	this->__vptr_Point3d_Point = __vptr_Point3d_Point;
	this->_z = rhs._z;
	return this;
}

同样的道理,Vertex的constructor也类似同样的处理策略,代码如下。

//C++伪代码
//在virtual base class情况下的constructor扩充内容
Vertex *
Vertex::Vertex(Vertex *this,bool __most_derived,
			float x,float y,float z)
{
	if(__most_derived != false)
	{
		this->Point::Point(x,y);
	}
	this->__vptr_Vertex = __vptr_Vertex;
	this->__vptr_Vertex_Point = __vptr_Vertex_Point;
	this->_z = rhs._z;
	return this;
}

在更深的继承情况下,例如Vertex3d,调用Point3d和Vertex的constructor时,总是会把__most_derived参数设为false,于是就压制了constructors中对Point constructor的调用操作。

//C++伪代码:
//在virtual base class情况下的constructor扩充内容
Vertex3d*
Vertex3d::Vertex3d(Vertex3d *this,bool __most_derived,
		float x, float y, float z)
{
	if(__most_derived != false)
	{
		this->Point::Point(x,y);
	}
	
	//调用上一层base classes
	//设定__most_derived为false
	
	this->Point3d::Point3d(false, x, y, z);
	this->Vertex::Vertex(false, x, y, z);
	
	//设定vptrs
	//安插user code
	
	return this;
}

2、Vptr初始化语意学

我们定义一个PVertex object时,constructors的调用顺序是:

Point(x,y);

Point3d(x,y,z);

Vertex(x,y,z);

Vertex3d(x,y,z);

PVertex(x,y,z);

假设这个继承体系中的每一个class都定义了一个virtual functions size(),此函数负责传回class的大小。如果我们写:

PVertex pv;

Point3d p3d;

Point *pt = &pv;

那么这个调用操作:

pt->size();

将传回PVertex的大小,而:

pt = &p3d;

pt->size();

将传回Point3d的大小。

进一步,我们假设这个继承体系中每一个constructor内含一个调用操作,像这样子:

Point3d::Point3d(float x,float y, float z)
{
	if(spyOn)
	{
		cerr << "Within Point3d::Point3d()"
			<<"size: "<<size()<<endl;
	}
}

在Point3d constructor中调用的size()函数,必须决议成Point3d::size()而不是PVertex::size()。

想一想,什么决定一个class的virtual functions名单的关键?答案是virtual table。Virtual table如何被处理?答案是通过vptr。所以为了控制一个class中所作用的函数,编译系统只要简单的控制住vptr的初始化和设定操作即可。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值