面试题答案
一键面试普通多继承情况下类C对象的内存布局
- 布局结构:
- 类C对象的内存布局通常是先存放从类A继承来的数据成员,然后接着存放从类B继承来的数据成员。如果类C有自己的数据成员,那么在类A和类B的数据成员之后存放。
- 例如,如果类A有数据成员
a
,类B有数据成员b
,类C有数据成员c
,那么类C对象的内存布局可能是a
、b
、c
的顺序(具体顺序可能因编译器实现而有所不同)。
- 示例代码(C++):
class A {
public:
int a;
};
class B {
public:
int b;
};
class C : public A, public B {
public:
int c;
};
在这种情况下,sizeof(C)
的值通常是sizeof(A) + sizeof(B) + sizeof(c)
(可能会考虑内存对齐等因素)。
虚继承情况下类C对象的内存布局
- 布局结构:
- 类C对象的内存布局会更加复杂。通常在对象开头会有一个指向虚基类表(vbtable)的指针(vptr),虚基类表中存储了偏移量等信息,用于找到虚继承的基类(这里是类A和类B的共同基类,如果存在的话)的数据成员位置。
- 类C自己的数据成员在内存中紧跟在这个指针之后,然后再存放通过虚继承从类A和类B继承来的数据成员(如果有的话)。
- 示例代码(C++):
class A {
public:
int a;
};
class B {
public:
int b;
};
class C : virtual public A, virtual public B {
public:
int c;
};
在这种情况下,sizeof(C)
的值除了包含a
、b
、c
的大小,还会包含vptr等额外开销。
虚继承解决的问题
- 菱形继承问题:
- 虚继承主要解决菱形继承带来的“数据冗余”和“二义性”问题。例如,假设有类D继承自类C,类C虚继承自类A和类B,类A和类B又都继承自同一个基类Base。在普通多继承下,类D中会存在Base类数据成员的两份拷贝,这就造成了数据冗余。同时,如果通过类D访问Base类的成员,会出现二义性,因为编译器不知道应该访问从类A继承来的Base类成员还是从类B继承来的Base类成员。
- 虚继承通过让所有虚继承的类共享一份基类数据成员,解决了数据冗余问题。并且由于只有一份基类数据成员,也解决了访问时的二义性问题。
内存布局差异的原因
- 普通多继承:
- 普通多继承按照简单的线性方式布局内存,每个基类的数据成员依次排列,这种方式简单直接,易于理解和实现,但会导致菱形继承时的数据冗余和二义性问题。
- 虚继承:
- 虚继承为了实现共享基类数据成员,需要额外的机制来定位虚基类的数据成员。通过引入虚基类表和vptr指针,能够在运行时准确找到虚基类数据成员的位置,虽然增加了内存开销和复杂性,但有效解决了菱形继承的问题。