自学内容网 自学内容网

C++菱形继承与虚继承

菱形继承问题

菱形继承问题是由于不合理的多继承引起的,B和C都继承自A,而D同时继承B和C,导致D中存在两份A的数据。

在这里插入图片描述

代码测试

class A{
public:
    int a;
};
class B : public A{
public:
    int b;
};
class C : public A{
public:
    int c;
};
class D: public B,public C{
public:
    int d;
};



int main() {
    cout << sizeof(A) << endl; // 4
    cout << sizeof(B) << endl; // 8
    cout << sizeof(C) << endl; // 8
    cout << sizeof(D) << endl; // 20
    return 0;
}

每一行后面的注释即为输出结果,如果D中只有一份A,那么D的大小应该是16字节,而输出的是20字节(两份a变量)。
在这里插入图片描述
当通过类D的对象访问成员变量a是,会提示变量A是无法确定的。
在这里插入图片描述
访问属性D对象的属性a需要指定来自哪个父类,这种数据冗余不仅带来了访问麻烦和数据冗余,而且也是不符合业务逻辑的。

虚继承解决菱形继承问题

class A{
public:
    int a;
};
class B : virtual public A{
public:
    int b;
};
class C : virtual public A{
public:
    int c;
};
class D: public B, public C{
public:
    int d;
};

int main() {
    cout << sizeof(A) << endl; // 4
    cout << sizeof(B) << endl; // 16
    cout << sizeof(C) << endl; // 16
    cout << sizeof(D) << endl; // 40

    D d;
    d.B::a = 11;
    d.C::a = 10;
    d.a = 9;
    cout << d.B::a << " " << d.C::a << " " << d.a << endl; // 9 9 9
    return 0;
}

采用虚继承后,d.B::ad.C::ad.a访问的都是同一份内存,以最后一次修改为准,即输出都是9。
虚继承会导致类对象的空间占用增大,是因为虚继承的实现原理主要通过虚基类指针(vbptr)和虚基类表(vbtable)来完成。

  • 虚基类指针(vbptr),当一个类采用虚继承方式继承基类时,编译器会为该派生类对象添加一个虚基类指针。这个指针在对象的内存布局中通常位于对象的开头位置,占用一个指针的存储空间(在 32 位系统中通常为 4 字节,64 位系统中通常为 8 字节)。虚基类指针指向一个虚基类表,该表中存储了与虚继承相关的信息,用于在运行时确定虚基类子对象在派生类对象中的位置。
  • 虚基类表(vbtable),是一个由编译器维护的数据结构,它不占用类对象的存储空间(其存储位置通常在程序的常量数据段等区域)。表中记录了虚基类与本类的偏移地址等信息。通过这些偏移信息,程序能够在运行时正确地找到虚基类子对象的位置。
  • 构造函数中的处理,在派生类的构造函数中,除了对直接基类和成员对象进行初始化外,对于虚基类的初始化也有特殊的处理。当使用虚继承时,虚基类的初始化通常在派生类构造函数的初始化列表中进行。并且无论继承的层次有多深,虚基类的子对象只会在最终的派生类构造函数中被初始化一次,以避免在多重继承中出现多个虚基类子对象的拷贝,这样就解决了菱形继承中的二义性问题和数据冗余问题。

扩展:当一个类同时存在虚函数和虚继承时,通常虚基类指针(vbptr)在对象内存布局的开头位置,而虚函数表指针(vftptr)紧随其后。


原文地址:https://blog.csdn.net/weixin_43364551/article/details/142926829

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!