首先需要说明的是,在实际的工程应用中,应该尽量拒绝使用菱形继承,因为这样很容易导致模糊调用。
那么,除了模糊调用外,菱形继承究竟会引发什么其他问题呢?我们先来看一个例子。
class A
{
public:
A(): m_a(2){}
public:
int m_a;
};
class B
{
public:
B() : m_b(3){}
public:
int m_b;
};
class C : public A, public B
{
public:
C() : m_c(4){}
public:
int m_c;
};
在代码中,类C从类A,B两个类中继承了两个成员m_a和m_b,所以类C的内存分布如下:
offset member
0 m_a
4 m_b
8 m_c
大小共12字节。这样的安排是很明晰的,如果在main函数中调用如下代码:
C c;
A* p = static_cast<A*>(&c);
B* t = static_cast<B*>(&c);
那么p指向了offset 0处,而t指向了offset 4处,这时使用p和t两个指针都不会出错,程序正常执行。
但是菱形继承完全打乱了这个计划。如果我们做如下修改:
class CBase
{
public:
CBase() : m_base(1){}
public:
int m_base;
};
class A : public CBase
.....
class B : public CBase
....
这样问题就来了,m_base成员是如何安排的?
我们打开VC进行调试,会发现类C这时会存在两个m_base成员,标识符分别为C::A::m_base和C::B::m_base,类C的大小一下子膨胀到20字节!这可不是我们想要的。
编译器这样做的原因可以从我们main函数中的两个cast转换中得到答案:
如果不实现两个m_base成员,那么将无法满足p和t两个指针有意义。
或许有人说,虚继承不是可以共享m_base成员吗?那好,我们看看虚继承是怎么做的:
class A : public virtual CBase
.....
class B : public virtual CBase
现在运行程序,在Watch中查看C的内存分布,C::A::m_base显示为1,而C::B::m_base变成了无法访问,p和t指针能成功地引用到m_base。虽然从内存分布上好像说不通,但看起来问题似乎得到了解决。
然而事情远没有想象中的那么简单,现在计算sizeof(C),结果让人大跌眼镜:竟然是24!类C的大小不但没有减少,反而增加了4字节!
我们来看Memory窗口中显示了什么:这里的类c的内存分布,&c = 0x0012FF68

注意红笔勾住的部分,这些数据是哪儿来的?这些数据对p指针和t指针对m_base成员的定位有什么关系?
看起来,这两数据像是一个地址,那么这个地址处放着什么?还是Memory窗口告诉了我们答案:
至于得到引用p->m_base和t->m_base的具体处理过程,这个问题就留给大家吧~~