• 虚继承及继承的内存布局


    转自: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的内存布局如下:

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

  • 相关阅读:
    Nginx 反向代理、负载均衡、页面缓存、URL重写及读写分离详解
    大型网站系统架构技术原理透析
    大中型网站架构探秘
    大型网站架构不得不考虑的10个问题
    (推荐)高并发高流量网站架构详解
    可扩展、高可用、负载均衡网站架构设计方案
    nginx内置常用变量
    Linux下nginx支持.htaccess文件实现伪静态的方法!
    扩展js,实现c#中的string.format方便拼接字符串
    Winform退出运行后,删除运行目录(批处理方法)
  • 原文地址:https://www.cnblogs.com/demian/p/6538301.html
Copyright © 2020-2023  润新知