• 虚函数 继承 多态


    单继承与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对象的成分没有被销毁,于是会造成诡异的“局部销毁”对象,这会形成资源泄露。

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

  • 相关阅读:
    关于python2.7的md5加密遇到的问题(TypeError: Unicode-objects must be encoded before hashing)
    Spring @Resource、@Autowired、@Qualifier区别
    互联网秒杀设计--转载
    学习动态性能表(5)--v$session
    学习动态性能表(4)--v$sqltext&v$sqlarea
    学习动态性能表(3)--v$sql&v$sql_plan
    学习动态性能表(2)--v$sesstat
    学习动态性能表(1)--v$sysstat
    Oracle备份与恢复案例
    日梭万年历网络版
  • 原文地址:https://www.cnblogs.com/Wojoin/p/4626408.html
Copyright © 2020-2023  润新知