多重继承
一个简单的例子(非虚拟)
class Top
{
public: int a;
};
class Left : public Top
{
public: int b;
};
class Right : public Top
{
public: int c;
};
class Bottom : public Left, public Right
{
public: int d;
};
使用UML图,我们可以把这个层次结构表示为:
注意Top被继承了两次。这意味着类型Bottom的一个实例bottom将有两个叫做a的元素(分别为bottom.Left::a 和 bottom.Right::a)。
Left、Right 和 Bottom在内存中是如何布局的,让我们先看一个简单的例子。Left
和 Right拥有如下的结构:
Left | Right |
---|---|
Top::a | Top::a |
left::b | right::c |
请注意第一个属性是从Top继承下来的。这意味着在下面两条语句后
Left* left = new Left();
Top* top = left;
Left 和 Top指向了同一地址,我们可以把Left Object当成Top Object来使用(很明显,Right与此也类似)。那Buttom呢?GCC的建议如下:
Buttom |
---|
Left::Top::a |
Left::b |
Right::Top::a |
Right::c |
Bottom::d |
如果我们提升Bottom指针,会发生什么事呢?
Bottom* bottom = new Bottom();
Left* left = bottom;
这段代码工作正常。我们可以把一个Bottom的对象当作一个Left对象来使用,因为两个类的内存部局是一样的。那么,如果将其提升为Right呢?会发生什么事?
Right* right = bottom;
为了执行这条语句,我们需要判断指针的值以便让它指向Bottom中对应的段。
Bottom | |
---|---|
Left::Top::a | |
Left::b | |
right --> | Right::Top::a |
Right::c | |
Bottom::d |
经过这一步,我们可以像操作正常Right对象一样使用right指针访问bottom。虽然,bottom与right现在指向两个不同的内存地址。出于完整性的缘故,思考一下执行下面这条语句时会出现什么状况。
Top* top = bottom;
是的,什么也没有。这条语句是有歧义的:编译器将会报错。
error: `Top' is an ambiguous base of `Bottom'
两种方式可以避免这样的歧义
Top* topL = (Left*) bottom;
Top* topR = (Right*) bottom;
执行这两条语句后,topL 和 left会指向同样的地址,topR 和 right也会指向同样的地址。
虚拟继承
为了避免重复继承Top,我们必须虚拟继承Top:
class Top
{
public: int a;
};
class Left : virtual public Top
{
public: int b;
};
class Right : virtual public Top
{
public: int c;
};
class Bottom : public Left, public Right
{
public: int d;
};
这就得到了如下的层次结构:
虽然从程序员的角度看,这也许更加的明显和简便,但从编译器的角度看,这就变得非常的复杂。重新考虑下Bottom的布局,可能是:
Bottom |
---|
Left::Top::a |
Left::b |
Right::c |
Bottom::d |
这个布局的优点是,布局的第一部分与Left的布局重叠了,这样我们就可以很容易的通过一个Left指针访问 Bottom类。
未完待续。。。
摘抄自 开源中国