• C++继承模型


    在C++继承模型中,一个派生类对象表现出来的东西。是其自己的成员加上其基类成员的总和。

    但这些成员怎样摆放。标准并未强制规定。一般而言,低地址放基类子对象,高地址放派生类对象。


    以下从四个部分讨论C++继承模型:
    • 单一继承不含虚函数
    • 单一继承并含虚函数
    • 多重继承
    • 虚拟继承
    1、单一继承不含虚函数

    这样的继承关系非常easy,基类子对象包括在了派生类对象中,在内存中连续存放。但有一点须要注意,把类分解成多层可能会造成空间的膨胀。

    比如:

    #include <iostream>
    #include <vector>
     
    using namespace std;
     
    class Foo {
    public:
        int val;
        char bit1, bit2, bit3;
    };
     
    class A {
    public:
        int val;
        char bit1;
    };
     
    class B : public A {
    public:
        char bit2;
    };
     
    class C : public B {
    public:
        char bit3;
    };
     
    int main()
    {
        cout << "size Foo = " << sizeof(Foo) << endl;
        cout << "size C   = " << sizeof(C) << endl;
        system("pause");
        return 0;
    }


    执行结果:


    两个类中包括相同的成员,空间却差了一倍,这是因为基类须要边界对齐的缘故。

    C++语言保证。出如今派生类中的基类子对象有其完整原样性,这是关键所在。为什么要使用这样牺牲空间的布局?原因是在对象之间拷贝时。仅仅对子对象进行成员拷贝而不影响派生类中的成员。


    2、单一继承并含虚函数
    基类中有虚函数。那么编译器会给基类生成一个virtual function table和一个vptr,派生类会继承此vptr,但不会指向同样的virtual function table,而是指向自己的virtual function table。毕竟派生类一般都会重写从基类继承的虚函数。关于vptr的摆放位置,要视编译器而定,要么放在对象开头,要么放在对象结尾。我手头的VS2013就把vptr放在了对象的开头处。



    可以看到,派生类Point3d中的vptr属于Point2d子对象。所以当一个基类指针指向派生类时,可以顺利取得这个vptr然后调用所需的虚函数以表现多态性。

    以下做个实验:
    #include <iostream>
    #include <vector>
     
    using namespace std;
     
    class Foo {
    public:
        int x;
    };
     
    class Bar : public Foo {
    public:
        int y;
        virtual void func()
        {}
    };
     
    int main()
    {
        Bar bar;
        cout << &bar << endl;
        cout << &bar.x << endl;
        cout << &bar.y << endl;
        system("pause");
        return 0;
    }


    执行结果:


    Foo类没有虚函数,也就没有vptr。

    而派生类Bar有虚函数。编译器把它的vptr插在了类的开头处。先于基类成员摆放。


    3、多重继承
    对一个多重派生对象。将其地址指定给派生列表“最左端”的基类指针,情况将和单一继承时同样,由于二者都指向同样的起始地址。

    对后继的基类指针的赋值,须要由编译器负责加上一个偏移地址。


    比如有例如以下继承结构:


    它的数据分布有可能例如以下:


    如果有例如以下定义:
    Vertex3d v3d;
    Vertex *pv;
    Point2d *p2d;
    Point3d *p3d;


    操作例如以下:
    pv = &v3d;
    p2d = &v3d;
    p3d = &v3d;


    对第一种赋值操作。编译器会产生类似以下的伪代码:
    pv = (Vertex *)(((char *)&v3d) + sizeof(Point3d));
    由于从布局图能够看出,指向Vertex的指针须要跳过开头的Point3d部分才干指向数据自己的子对象。然后。对第二和第三种赋值操作,仅仅须要简单地拷贝其地址就好,由于Point2d指针和Point3d指针都指向对象v3d的起始地址。


    4、虚拟继承
    因为虚拟基类是共享的,所以在各个派生类中必需要由编译器加入某种信息,用来保存共享的虚拟基类的地址。关于怎样加入。各个编译器厂家的实现都有所不同,并且在未来也会有更新,这里就不详细说明了。除了通过对象来存取虚基类中的成员之外。通过指针和引用都会引起执行成本上的额外开销。一般而言,虚基类最有效的一种运用形式就是:一个抽象的虚基类。没有不论什么数据成员。


    參考:
    《深度探索C++对象模型》 P99-P123.
    
    
  • 相关阅读:
    SpringBoot中Word转PDF
    使用Word模板导出标准表Word样式文件
    Chrome 80及以上版本 中 Iframe 跨域 Cookie 的 Samesite 问题
    Visual Studio 2022 Key
    《HelloGitHub》第 69 期
    重玩 40 年前的经典游戏小蜜蜂,这次通关了源码
    那些年的开源项目,你跑起来了吗?
    图片处理看这篇就完了「GitHub 热点速览 v.21.48」
    年底巩固下 CS 知识「GitHub 热点速览 v.21.49」
    误入 GitHub 游戏区,意外地收获颇丰
  • 原文地址:https://www.cnblogs.com/claireyuancy/p/6905648.html
Copyright © 2020-2023  润新知