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时