详解C++中菱形继承的原理与解决方法
作者:谜一样的男人1
菱形继承形成原因
多继承,呈菱形状
菱形继承代码:
class A { public: A() {} int _a ; }; class B :public A { public: //会存在父的int a; B() {} int _b ; }; class C :public A { public: //会存在父的int a; C() {} int _c ; }; class D :public B,public C { public: //会存在父的int a; 问题来了这个a是 父亲b中的a 还是 父亲c中的a -- 二义性; //父的int b; //父的int c; D() {} int _d ; }; int main() { D d; d.B::_a = 1; d.C::_a = 2; d._b = 3; d._c = 4; b._d = 5; cout << d._a << endl;//报错 提示b.a具有二义性 return 0; }
出现二义性变量的内存布局
可以看到上图中紫色框两部分,是出现二义性的两份A::_a变量,编译器无法自主确定需要用哪一个,可以d::A_a 或者 d::B_a这样使用;
应对方案
虚继承 vitrual
vitrual修饰使派生类出现二义性的父类继承部分(菱形的腰部)
class A { public: A() {} int _a ; }; class B :virtual public A //virtual修饰 { public: //父的int a; B() {} int _b; }; class C :virtual public A //virtual修饰 { public: //父的int a; C() {} int _c ; }; class D :public B,public C { public: //自己的 int a;(二义性的父亲继承经virtual修饰) //父的int b; //父的int c; D() {} int _d; }; int main() { D d; d.B::_a = 1; d.C::_a = 2; d._b = 3; d._c = 4; b._d = 5; cout << d._a << endl;//没问题,输出2,这个2是D类成员通过继承,使得d自己独有且只有一份的 A::_a成员变量; return 0; }
解决二义性变量内存布局–虚基表
可以看到原先存放两个二义性数据A::a和B::a的位置,变成了一个地址?
这个两个地址就叫虚基表指针;
通过对虚基表的进一步内存研究,发现了虚基表紧着得下一个位置存放了一个偏移量,这个偏移量是存放该虚基表指针的内存位置,与当前派生类独有一份的成员变量_a之间的偏移长度;
这样我们直接使用d::a的时候,因为独有一份,也不会出现数据二义性的问题了;
注意,这和多态中的虚表(虚函数表两回事)
其次,虚基表的指针,经过测试,也是某个多继承派生类的多对象共用的;
eg: D d1,d2; 其中d1,d2两个对象 上图的这两张虚基表指针是一样的,原因很简单,D类类型都一样,那么某个位置起,到另一个相对位置偏移量肯定是固定的!
(同类型的虚表指针也如此,复用节省空间嘛)
感悟
继承,多态无疑为我们创造了很多的价值,但是像菱形继承这种弊端也是存在的,本质是多继承而引起的问题,在一些语言中禁止了多继承的行为,总之有利有弊,虽然C++允许了多继承,但还是尽量别写多继承这种模式,复杂度和出现问题的概率都很大;
关于代码复用等的另一种关系-组合
继承是每个派生类都能相对于继承is-a的关系; 每个派生类对象都是一个基类对象; 耦合度高
// Car和BMW Car和Benz构成is-a的关系 class Car{ protected: string _colour ="白色"; // 颜色 string _num ="陕ABITB";// 车牌号 }; class BMW:public Car{ public: void Drive() {cout <<"好开-操控"<< end1;} }; class Benz : public Car{ public: void Drive() {cout <<"好坐-舒适"<< endl;} };
优先考虑组合has-a的关系; eg: 汽车-轮胎,has-a,组合(class 轮胎 作为class 车的成员嵌套) 耦合度低
// Tire和Car构成has-a的关系 class Tiref{ protected: string _brand = "Michelin";// 品牌 size_t size = 17;// 尺寸 }; class Car{ protected: string _colour ="白色";// 颜色 string _num ="陕ABITO";// 车牌号 Tire _t;// 轮胎 };
到此这篇关于详解C++中菱形继承的原理与解决方法的文章就介绍到这了,更多相关C++菱形继承内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!