继承体系下的对象构造

在这里插入图片描述

1.先内含一个类

类声明

class Point
{
public :
     Point(int x = 0, int y = 0):X(x), Y(y){};
     Point(const Point& Temp);
     Point& operator=(const Point& Temp);

     virtual ~Point() {};
     virtual int z() { return 0; }

     inline int getX(void) const { return X; }
     inline int getY(void) const { return Y; }
     inline void setX(int x) { X = x; }
     inline void setY(int y) { Y = y; }
protected:
     int X, Y;
};


class Line
{
public:
     Line(int x0, int y0, int x1, int y1);
     Line(const Point& begin, const Point& end) 
     			: Begin(begin), End(end) {}



protected:
     Point Begin, End;
};

某些实现


Line::Line(int x0, int y0, int x1, int y1)
{
     this->Begin.setX(x0);
     this->Begin.setY(y0);
     this->Begin.setX(x1);
     this->Begin.setY(y1);
}
Point&  Point::operator=(const Point& Temp)
{
     this->X = Temp.X;
     this->Y = Temp.Y;
     return *this;
}

Point::Point(const Point& Temp)
{
     this->X = Temp.X;
     this->Y = Temp.Y;
}

调用

int main()
{

     Line a(1,2,3,4);

     Line b = a;

     return 0;
}

结果
在这里插入图片描述

①我们可以看到Line这个类中内含了另外一个类的对象,而且Line并未显示拥有一个=赋值运算符的重载,也没有拥有一个拷贝构造函数 ,但是却可以把一个Line b = a(拷贝构造函数),c = a;(赋值运算符的重载)
所以编译器为我们隐式生成了这些函数
②当内含一个对象的时候,会先调用内含对象的构造函数,在调用自身的构造函数
③由于Point声明了一个拷贝构造函数,一个重载赋值运算符,一个虚析构函数,所以Line的隐式拷贝构造函数,隐式重载赋值运算符,和析构函数(继承是虚的,本例子中是内含 是nontrivial)
可能生成的Line的析构函数

Line::~Line(Line *this)
{
	this->End.Point::~Point();
	this->Begin.Point::~Point();
}

2.构造函数调用顺序

1、先调用基类的构造函数,调用顺序与继承顺序保持一致

2、再调用派生类中的内含的对象的构造函数,调用顺序与内含对象的声明顺序保持一致,与初始化列表中的顺序无关。

3、最后调用派生类的构造函数。

而析构函数与构造函数的顺序完全相反。

3.虚拟继承(Virtual Inheritance)

class Point3d : public virtual Point
{
public:
	Point3d(float x = 0.0,float y = 0.0,float z = 0.0)
		:Point(x,y),_z(z){}
	Poin3d(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 = 0.0,float y = 0.0,float z = 0.0)
{
	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的subobject时,它们对Point constructor掉头操作一定不可以发生,应该是作为最底层的class,有责任去将Point初始化。

传统的策略情况下扩充的constructor,会导致constructor中更多的扩充内容,用以指示virtual base class constructor应不应该被调用,constructor的函数体必须因条件测试参数,决定调不调用相关的virtual base class constructors。如下是正确的Pointd3d的constructor:

//C++伪码
//在virtual base class情况下的constructor扩充
Point3d*
Point3d::Point3d(Point3d* this,
				float x = 0.0,float y = 0.0,float z = 0.0)
{
	if(_most_derived != false)
		this->Point::Point(x,y);
	
	this->vptr_Point3d = _vtbl_Point3d;
	this->_vptr_Point3d_Point = _vtbl_Point3d_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,
				float x = 0.0,float y = 0.0,float z = 0.0)
{
	if(_most_derived != false)
		this->Point::Point(x,y);
	//调用上层base classes
	//设定_most_derived为flase
	this->Point3d::Point3d(false,x,y,z);
	this->Vertex::Vertex(false,x,y);
	
	//安插vptrs
	//安插user code
	
	return this;
}

这样策略下以保证语意的正确无误,当我们定义Point3d和Vertex3d cv时,都可以正确的调用Point constructor。

某些编译器支持把一个constructor一分为二,一个针对完整的object定义,另一个针对如果完整object是subobject;完整的版本,无条件调用virtual base constructor,设置所有的vptr;subobject版本则不调用virtual base constructor,也可能不设定vptr。

4.vptr初始化语意学(The Semantics of the vptr Initialization)

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

Point(x,y);
Point3d(x,y,z);
Vertex(x,y,z);
Vertex3d(x,y,z);
PVertex(x,y,z);

假设这个继承体系中每一个class都定义了virtual function size(),此函数返回class的大小。当我们如下操作,传回PVertex大小:

PVertex pv;
Poin3d p3d;
 
Point* pt = & pv;
pt->size();

假设这个集成体系中每一个constructor内含一个调用操作:
就是在每一个基类的构造函数内部写下一个虚函数size()

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

那么 他会产生多态调用吗?
对于如上调用而言,如果调用操作限制在constructor(或destructor)中,每一个调用都是静态的,不会是动态的

在执行一个constructor时,必须限制一组virtual function候选名单,决定virtual function的候选名单的virtual table。virtual table是通过vptr来处理的。所以编译器为了控制一个class中所有作用的函数,编译器系统只要简单的控制vptr的初始化和操作即可。设定vptr是编译器的责任,程序员不需要操心。

vptr在constructor中的初始化时间是:在base class constructors调用操作之后,但是在程序员提供的代码或是member initialization list中所有的member初始化操作之前。

每一个constructor都一直等到起base class constructors执行完毕之后才设定其对象的vptr,那么每次它都能够正确的调用virtual function实例

令每一个base class constructor设定其对象的vptr,使它指向相关的virtual table之后,构造中的对象就可以严格而正确的“构造过程中所幻化出来的每一个class”的对象。constructor执行通常如下:

①在derived class constructor中,“所有virtual base classes”及“上一层base class”的constructor会被调用。
②上述完成之后,对象的vptr被初始化,指向vtbl。
③如果有member initialization list的话,将在constructor体内扩展开来,这必须在vptr被设定之后才做,以免有一个virtual member function被调用。
④执行程序员所提供的代码。
构造函数的调用顺序 从根到叶子 从内到外 也就是说只有当自己的构造函数被调用完成之后,才是真正的构造完毕

vptr必须设定的情况
1.当一个完整的对象被构造起来的时候。
②当一个subobject constructor 调用了一个virtual function时

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值