✏️

Inheritance

Tags
C++支持单继承和多重继承
 

钻石继承 + 虚继承

虚继承:
class B: virtual A {};
钻石继承:
class Base { public: int data; }; class Derived1 : public Base { }; // 继承Base class Derived2 : public Base { }; // 继承Base class Final : public Derived1, public Derived2 { }; // 多重继承,产生二义性
不用虚继承的钻石继承存在的问题:
  • 二义性
Final obj; obj.data = 10; // 编译错误,歧义:不知道访问 Derived1::data 还是 Derived2::data
  • 数据冗余:存在两份Base的成员内存,且逻辑不一致
 
在钻石继承的情况下,采用虚继承则只会保留一份继承者的实例
  • 底层原理
在对象的vftptr之后存储一个指向“虚继承父类的内存”位置base与当前位置之差的指针(base可能指向一个vftptr也可能指向第一个字段)
并且Base类的初始化由Final直接执行,忽略中间类对Base类的初始化
内存布局见
✏️
Memory Model
 

钻石继承+虚继承中虚方法的二义性

这种写法一定是不对的,避免这样写
class A { // 虚基类 public: virtual void f0() {} }; class B : virtual public A { // 虚继承 public: void f0() override {} // 重写f0 }; class C : virtual public A { // 虚继承 public: void f0() override {} // 重写f0 }; class D : public B, public C { // 非虚继承(普通多重继承) // 未重写f0 };
无法通过编译,但疑似部分编译器可以通过类的定义
notion image
D d; d.f0(); // 编译错误:对'f0'的调用不明确 D* dd = new D(); dd->f0(); // 同上
对于这部分可以通过的编译器,如果通过虚基类指针访问,则会是UB,根据编译器不同会有不同表现
典型行为:调用在继承列表中先出现的类(B)的实现
A* pa = &d; pa->f0(); // 未定义行为(取决于编译器实现)