摘要:本文围绕 C++ 继承体系展开,先介绍多继承的基本概念与二义性处理方式,再分析菱形继承带来的二义性和数据冗余问题,进而引出菱形虚拟继承的解决方案,说明其通过虚基表与偏移量寻址来保证单个对象内基类成员只有一份。最后总结继承关系要点,并澄清虚基表共享与对象数据共享之间的常见误区。
目录
一、多继承
二、菱形继承
二义性问题
数据冗余问题
三、菱形虚拟继承
四、总结
五、误区总结
一、多继承
多继承:一个派生类有两个或两个以上的直接基类,称为多继承。出现二义性可以用指定类型访问来解决。
二、菱形继承
菱形继承会带来二义性和数据冗余问题
二义性问题
可以通过显示指定类型访问,但是数据冗余还没有解决
class A { public: int _a=1; }; class B : public A { public: int _b=2; }; class C : public A { public: int _c=3; }; class D : public B, public C { public: int _d = 4; }; int main() { D d; d._a = 5;//出现二义性,B中有_a,C中有_a,编译器不知道调用哪个_a }数据冗余问题
数据冗余是派生类对象模型有基类的成员数据重复出现,造成数据冗余,浪费空间。
三、菱形虚拟继承
为了解决菱形继承的二义性和数据冗余问题,C++引入了菱形虚拟继承的概念。虚拟继承确保基类的共享部分只有一份,避免了重复继承。
四、总结
继承关系
单继承:一个派生类只有一个直接基类时为单继承
多继承:多个派生类继承同一个基类,称为多继承
菱形继承:两个派生类继承同一个基类,又有某个类继承这两个派生类
菱形继承总结
菱形继承是两个派生类继承同一个基类,又有某个类继承这两个派生类,但是菱形继承会出现数据冗余和二义性的问题,虽然可以通过指定类型访问来解决二义性的问题,但是数据冗余问题还没解决,所以可以使用菱形虚拟继承来解决二义性和数据冗余的问题,保证一个对象内只存一份基类成员。
菱形虚拟继承的原理是,派生类不会继承基类的成员变量,而是派生类的对象内存中有虚基表指针,虚基表指针指向虚基表,虚基表里面存放的是偏移量,可以通过指针找到偏移量,通过偏移量+自己的地址找到基类成员。
虚机表本质/虚基类成员寻址/虚继承
虚基表是类级别的静态元数据,不属于对象。同一个类的所有对象共用唯一一张虚基表,表里存储的是编译期就确定的固定偏移常量,程序运行全程只读不变。
虚基类成员的寻址公式是:成员真实地址 = 当前对象首地址 + 虚基表偏移量。
虽然所有对象的偏移寻址规则完全一致,但每个对象的内存首地址相互独立,所以访问到的都是自己内部独有的虚基类成员,对象之间数据完全隔离、互不干扰。
虚继承的核心作用,只是解决单个对象内部菱形继承带来的基类多副本和二义性问题,保证一个对象内只存一份基类成员。它不会让多个对象共用同一份数据,修改某个对象的成员,只会改动当前实例内存,不影响其他对象,也不会修改虚基表。
五、误区总结
在学习 C++ 菱形虚继承内存模型时,容易混淆 虚基表共享 和 对象数据共享。误以为:多个对象共用同一张虚基表、同一套偏移量,就会共用同一份基类成员数据。
实际上是共用虚基表、共用寻址规则,只是共用一套计算公式,不共用内存数据。
误区 1:认为多个 D 对象会共用同一份 _a 成员
误以为:所有对象共用同一张虚基表、同一个偏移量,所以所有对象共享同一个 _a,改一个全变。
实际上不共享。虚继承只解决单个对象内部的成员冗余,不会跨对象共享数据。
误区 2:误以为虚基表存储对象的绝对内存地址
误以为:虚基表里存着 _a 的地址,所以多个对象会定位到同一块内存。
实际上:虚基表只存编译期固定偏移常量,不存任何地址、不存任何数据。
误区 3:误以为创建越多对象,虚基表会新增、扩容偏移项
误以为:d、d1 是不同对象,虚基表需要存储多个不同偏移量对应不同对象。
实际上:虚基表属于类级别,不属于对象。无论创建多少个实例,一张表、一组偏移量,永久不变。
误区 4:核心根源误区
只看到:虚基表相同、偏移量相同、虚基指针相同,每个对象的起始内存地址完全独立
在学习 C++ 菱形虚继承内存模型时,容易混淆 虚基表共享 和 对象数据共享。 误以为:多个对象共用同一张虚基表、同一套偏移量,就会共用同一份基类成员数据。 class A{ public: int _a; }; // 虚继承 A class B: virtual public A { public: int _b; }; // 虚继承 A class C :virtual public A { public: int _c; }; // 菱形继承:同时继承 B、C class D : public B , public C { public: int _d; }; int main() { D d; D d1; return 0; }