虚函数 继承 多态

单继承与Data Members

在C++的继承模型中,base class members和derived class members的排列顺序并为强制规定。不同的编译器可能有不同的布局安排。大部分情况下,base class members会安排在derived class members的前面,但base class是virtual base class(base class存在virtual function)除外。

只有继承没有多态

考虑如下程序:

class Point2d

{

public:

     Point2d(float x = 0.0, float y = 0.0)

         :x(x),y(y){}

 

     void setX(float newX)

     {

         x = newX;

     }

     void setY(float newY)

     {

         y = newY;

     }

     float getX()

     {

         return x;

     }

     float getY()

     {

         return y;

     }

 

     void operator+=(const Point2d& rhs)

     {

         x += rhs.getX();

         y += rhs.getY();

     }

protected:

     float x,y;

};

 

class Point3d : public Point2d

{

public:

     Point3d(float x = 0.0,float y = 0.0,float z = 0.0)

         :Point2d(x,y),z(z){}

 

     void  setZ(float newZ)

     {

         z = newZ;

     }

     float getZ()

     {

         return z;

     }

 

     void operator+=(const Point3d& rhs)

     {

         Point2d::operator +=(rhs);

         z += rhs.getZ();

     }

protected:

     float z;

 

};

这样单一继承且没有virtual function的数据布局如下:


加上多态(加上虚函数)

对于Point2d而言,它只是特殊的Point3d,其中的z等于0。这样Point2d,Point3d的重新设计如下,新增部分以红色标出

class Point2d

{

public:

     Point2d(float x = 0.0, float y = 0.0)

         :x(x),y(y){}

 

     void setX(float newX)

     {

         x = newX;

     }

     void setY(float newY)

     {

         y = newY;

     }

     float getX()

     {

         return x;

     }

     float getY()

     {

         return y;

     }

 

     virtual void setZ(float){}

     virtual float getZ()

     {

         return 0.0;

     }

     virtual void operator+=(const Point2d& rhs)

     {

         x += rhs.getX();

         y += rhs.getY();

     }

protected:

     float x,y;

};

 

class Point3d : public Point2d

{

public:

     Point3d(float x = 0.0,float y = 0.0,float z = 0.0)

         :Point2d(x,y),z(z){}

 

     void  setZ(float newZ)

     {

         z = newZ;

     }

     float getZ()

     {

         return z;

     }

 

     void operator+=(const Point2d& rhs)

     {

         Point2d::operator +=(rhs);

         z += rhs.getZ();

     }

protected:

     float z;

};

说下虚函数的实现,在大部分编译器中虚函数是通过virtual table和virtual table pointer实现的,二者可以简写为vtbl和vptrs。vtbl通常以函数指针实现。在程序中凡是声明(或者继承)了虚函数者,都有自己的一个vtbl,而其中的值就是该class的各个虚函数实现体的指针。而vptrs的作用就是提供执行期的链接,使每一个object能够找到相应的vtbl。关于vptrs到底放在class object的哪里好?(一般是在class object的最前面或者最后面),不同的编译器有不同的安排。此时class object的布局如下(此图是把vptr放在base class的尾端):

 

 

由上图可见base class与derived class之间的转换可以很自然的进行,因为base class和derived class的object都是从相同的地址开始,唯一的差异只是derived object比较大,用以容纳自己的non-static data members。如进行一下操作:

Point3d p3d;

Point2d *p = &p3d;

把一个derived class object指定给base class的指针或者reference,该操作并不需要编译器去修改地址,它可以自然地发生。

多重继承

加如一个新的类Vertex如下:

class Vertex

{

public:

         //拥有若干virtual接口,所以Vertex对象中会有vptr

protected:

         Vertex* next;

}

 

class Vertex3d: public Point3d.public Vertex

{

public:

         // ….

protected:

         float mumble;

}

 

 

 

 

现在Point2d,Point3d,Vertex,Vertex3d的继承关系如下

 

 

多重继承的问题主要发生于derived class objects和其第二或后继的base class objects(如本例中的Vertex3d到Vertex之间的转换就属于这中情况)之间的转换,而不是像单重继承那样的转换或是经由其支持的virtual function做的转换(暂且可以不考虑)。

对一个多重派生对象,将其地址指定给第一个base class的指针情况和单一继承时相同,因为二者都是指向相同的起始地址,需要付出的成本只有地址的指定操作。至于第二个或后继的base class的地址指定操作,需要将地址修改为加上(或减去)介于中间的base class subobject的大小。

上例的数据布局如下图:

 

 

如下例:

Vertex3d v3d;

Vertex* pv;

Point2d* p2d;

Point3d* p3d;

则下面的操作

pv = &v3d; //相当于单重继承的转换

编译器内部的转换可能是这样的:

pv =(Vertex*)( ((char*)&v3d)  +  sizeof(Point3d) );   //加上sizeof(Point3d的原因是Vertex3d继承了Point3d

而下面的指定操作

p2d = &v3d;

p3d = &v3d;

都只需要简单地拷贝其地址就行了。

 

C++ Standard 中并未要求base class Point3d,和Vertex有特定的排列次序。

虚拟继承

多重继承中会碰到如下图的继承关系(钻石型继承),C++中的ios,istream,ostream,iostream就是典型的钻石型继承。

 

 

 

这样的继承出现时, base class 的data members会在derived class object中都出现,这样derived class object就会出现同样的数据或者function。此时,让base class成为virtual(虚基类),可以消除这样的冗余现象,唯一需要付出的成本是derived class object内有两指针指向虚基类(如:本例的类A)。

若类A没有任何虚函数,D对象的内存布局可能如下:

D object

B data members

Point to virtual base class

C data members

Point to virtual base class

D data members

A data members

 

 

 

 

若类A存在任何虚函数,D对象的内存布局可能如下:

B data members

vptr

Point to virtual base class

C data members

vptr

Point to virtual base class

D data members

vptr

A data members

 

Virtual Destructor

最后想说下virtual destructor,我们经常看到基类会把析构函数写成虚函数,这样写还是有道可循的。

如下程序

class Base {

public:

         virtual Base() {};

};

class Derived : public Base {};

Base* pd = new Derived;

但是,当你写下delete pd;时,如果base class 的destructor不是virtual的,其结果是未定义的。实际上执行时通常会发生的是derived对象的成分没有被销毁,于是会造成诡异的“局部销毁”对象,这会形成资源泄露。

上面如有不足不对的地方,望各路大神补充指正。

posted @ 2015-07-07 11:15  Wojoin  阅读(359)  评论(0编辑  收藏  举报