菱形继承、虚继承、虚基类和虚继承下的内存模型
一、 菱形继承
菱形继承,是指派生类D的两个或多个直接基类B、C,继承自同一个或多个间接基类A,那么在派生类D中存在继承自间接基类A的两份成员变量。
这种情况下很容易出现命名冲突的情况。比如基类A中存在一个成员变量int a,在派生类D中使用的时候必须要利用类名和域解析符去做强调。
void seta(int a){ B::m_a = a; }
void setb(int b){ C::m_b = b; }
二、虚继承
1.为了解决命名冲突和数据冗余问题,引入虚继承
此时需要将两个直接基类B、C继承间接基类A方式上加上virtual,使得在派生类中只保留一份间接基类的成员
//间接基类A
class A{
protected:
int m_a;
};
//直接基类B
class B: virtual public A{ //虚继承
protected:
int m_b;
};
//直接基类C
class C: virtual public A{ //虚继承
protected:
int m_c;
};
此时,共享基类A被称为虚基类,
2.虚继承成员变量
因为在虚继承的最终派生类中只保留了一份虚基类的成员,所以该成员可以被直接访问,不会产生二义性。此外,如果虚基类的成员只被一条派生路径覆盖,那么仍然可以直接访问这个被覆盖的成员。但是如果该成员被两条或多条路径覆盖了,那就不能直接访问了,此时必须指明该成员属于哪个类。
3.虚继承构造函数与初始化
不存在虚继承时,初始化仅会调用直接基类的构造函数,但是存在虚继承时,会调用间接基类的构造函数。
在最终派生类的构造函数调用列表中,不管各个构造函数出现的顺序如何,编译器总是先调用虚基类的构造函数,再按照出现的顺序调用其他的构造函数;而对于普通继承,就是按照构造函数出现的顺序依次调用的。
4.虚继承的内存模型
对于普通继承的方式,obj_a、obj_b、obj_c、obj_d 的内存模型如下所示:
A 是最顶层的基类,在派生类 B、C、D 的对象中,A 类子对象始终位于最前面,偏移量是固定的,为 0。b1、b2 是派生类 B 的新增成员变量,它们的偏移量也是固定的,分别为 8 和 12。c1、c2、d1、d2 也是同样的道理。
对于存在虚继承的内存模型,大部分编译器会把基类成员变量放在派生类成员变量的后面,这样随着继承层级的增加,基类成员变量的偏移就会改变,就得通过其他方案来计算偏移量。
若A是B的虚基类:
若A是B的虚基类,B是C的虚基类:
- 不带阴影的一部分偏移量固定,不会随着继承层次的增加而改变,称为固定部分;
- 带有阴影的一部分是虚基类的子对象,偏移量会随着继承层次的增加而改变,称为共享部分。