尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

C++菱形继承:从二义性到虚基表寻址的解析过程

C++菱形继承:从二义性到虚基表寻址的解析过程 摘要本文围绕 C 继承体系展开先介绍多继承的基本概念与二义性处理方式再分析菱形继承带来的二义性和数据冗余问题进而引出菱形虚拟继承的解决方案说明其通过虚基表与偏移量寻址来保证单个对象内基类成员只有一份。最后总结继承关系要点并澄清虚基表共享与对象数据共享之间的常见误区。目录一、多继承二、菱形继承二义性问题数据冗余问题三、菱形虚拟继承四、总结五、误区总结​​​​​​​一、多继承多继承一个派生类有两个或两个以上的直接基类称为多继承。出现二义性可以用指定类型访问来解决。二、菱形继承菱形继承会带来二义性和数据冗余问题二义性问题可以通过显示指定类型访问,但是数据冗余还没有解决class A { public: int _a1; }; class B : public A { public: int _b2; }; class C : public A { public: int _c3; }; 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; }
返回列表