zoukankan      html  css  js  c++  java
  • 虚继承及继承的内存布局

    转自:http://blog.csdn.net/xsh_123321/article/details/5956289

    1.为什么需要虚继承

    如下图所示如果访问Der::Fun or Der::m_nValue就会带来二义性,无法确定是调用Base1的还是Base2的,所以为了解决多重继承情况下成员访问的二义性,引入了虚继承机制。
    一般继承:
    一般继承

    虚继承:
    虚继承

    2.虚继承实现

    在虚继承下,Der通过共享虚基类SuperBase来避免二义性,在Base1,Base2中分别保存虚基类指针,Der继承Base1,Base2,包含Base1, Base2的虚基类指针,并指向同一块内存区,这样Der便可以间接存取虚基类的成员,如下图所示:

    3.不同编译器实现方式

    不同编译器对间接存取的方法不同,以下以GCC和VC为例,均采用以下代码进行实验:

    class SuperBase
    {
    public:
        int m_nValue;
        void Fun(){}
        virtual ~SuperBase(){}
    };
    class Base1:  virtual public SuperBase
    {
    public:
    virtual ~ Base1(){}
    };
    class Base2:  virtual public SuperBase
    {
    public:
    virtual ~ Base2(){}
    };
    class Der:public Base1, public Base2
    {
    public:
    virtual ~ Der(){}
    };

    1) GCC中结果为8, 12, 12, 16

    解析:sizeof(SuperBase) = sizeof(int) + 虚函数表指针

    sizeof(Base1) = sizeof(Base2) = sizeof(int) + 虚函数指针 + 虚基类指针

    sizeof(Der) = sizeof(int) + Base1中虚基类指针 + Base2虚基类指针 + 虚函数指针

    GCC共享虚函数表指针,也就是说父类如果已经有虚函数表指针,那么子类中共享父类的虚函数表指针空间,不在占用额外的空间,这一点与VC不同,VC在虚继承情况下,不共享父类虚函数表指针,详见如下。

    2)VC中结果为:8, 16, 16, 24

    解析:sizeof(SuperBase) = sizeof(int) + 虚函数表指针

    sizeof(Base1) = sizeof(Base2) = sizeof(int) + SuperBase虚函数指针 + 虚基类指针 + 自身虚函数指针

    sizeof(Der) = sizeof(int) + Base1中虚基类指针 + Base2中虚基类指针 + Base1虚函数指针 + Base2虚函数指针 + 自身虚函数指针

    如果去掉虚继承,结果将和GCC结果一样,A,B,C都是8,D为16,原因就是VC的编译器对于非虚继承,父类和子类是共享虚函数表指针的。

    继承中的内存布局

    1. 虚函数指针(vptr)放最前,之后放变量。
    2. 多个父类排着放,再放子类
    3. 子类的覆盖的虚函数将所有祖先的同名虚函数都覆盖。
    4. 子类其它的虚函数指针放在第一个父类的虚函数表里。
    5. 虚拟继承的情况只需要在钻石继承中有必要使用(避免二义性),子类中最先的祖先放最后。

    多说不如举例,看下面转的文章。

    本文章转自:http://blog.csdn.net/randyjiawenjie/article/details/6693337

    分为四种情况:
    1.单继承

    2.多继承(不含钻石继承)

    3.非虚继承的钻石继承

    4.虚继承的钻石继承

    注:下面所有类中的函数都是虚函数。

    1.单继承

    单继承体系如下:

    GrandChild对象的内存布局:

    可见以下几个方面:

    1)虚函数表在最前面的位置。

    2)成员变量根据其继承和声明顺序依次放在后面。

    3)在单一的继承中,被overwrite的虚函数在虚函数表中得到了更新。

    2.多继承

    多继承的体系如下:

    Derive对象的内存布局如下:

    我们可以看到:

    1) 每个父类都有自己的虚表。

    2) 子类的成员函数被放到了第一个父类的表中。

    3) 内存布局中,其父类布局依次按声明顺序排列。

    4) 每个父类的虚表中的f()函数都被overwrite成了子类的f()。这样做就是为了解决不同的父类类型的指针指向同一个子类实例,而能够调用到实际的函数。

    出现钻石继承的虚继承的时候,虚基类在子类中只有一份。

    出现钻石继承的非虚继承的时候,虚基类在每个子类中都有一份。

    3.非虚继承的钻石继承

    继承体系如下:

    D的内存布局如下:

    红色的部分就是重复的部分,就会造成二义性

    4.虚继承的钻石继承

    (虚继承就是解决钻石继承问题的,如果不存在钻石继承,就不用虚继承)

    继承体系如下:(红色专门标准虚继承)


    D的内存布局如下:

    可以看出,少了重合的部分。但是,代价是增加了一个虚函数指针。

  • 相关阅读:
    新一代MQ apache pulsar的架构与核心概念
    Flutter使用fluwx实现微信分享
    BZOJ3622 已经没有什么好害怕的了 动态规划 容斥原理 组合数学
    NOIP2016提高组Day1T2 天天爱跑步 树链剖分 LCA 倍增 差分
    Codeforces 555C Case of Chocolate 其他
    NOIP2017提高组Day2T3 列队 洛谷P3960 线段树
    NOIP2017提高组Day2T2 宝藏 洛谷P3959 状压dp
    NOIP2017提高组Day1T3 逛公园 洛谷P3953 Tarjan 强连通缩点 SPFA 动态规划 最短路 拓扑序
    Codeforces 873F Forbidden Indices 字符串 SAM/(SA+单调栈)
    Codeforces 873E Awards For Contestants ST表
  • 原文地址:https://www.cnblogs.com/demian/p/6538301.html
Copyright © 2011-2022 走看看