两种实现:http://blog.youkuaiyun.com/bluedog/article/details/4711169
虚继承:http://www.oschina.net/translate/cpp-virtual-inheritance
虚函数:http://my.oschina.net/pathenon/blog/67072
当c++支持virtual base class 时,就会多了一些额外负担,当class 中内含一个或多个virtual base class subobject时,将分成两个部分,一个不变局部和一个共享局部。最初的方案是为每一个虚基类安插一个指针指向这个虚基类,其缺点是为了负担太重,而且当虚继承链加长时,导致间接存取时间加长(需通过多次跳转)。因此有两种解决方案(《深入》一书中所提):
一、是引入virtual base class table,不管多少个虚基类,总是只有一个指针指向它,这个virtual base class table(VBTBL)包括真正的 virtual base class 指针。
二、Bjarne的办法是在virtual function table中放置virtual base class的offset,而非地址,这个offset在virtual function table 的负位置(正值是索引virtual function,而负值则方向盘引到virtual base class offsets)。
code blocks中第二种方法Bjarne
struct B1
{
int a;
int b;
};
struct B2
{
virtual void foo(void);
int c;
int d;
};
struct Test : virtual public B1, virtual public B2
{
virtual void func1(void);
virtual void func2(void);
virtual void func3(void);
int X;
};
![]() zaobao
|
多重继承首先我们考虑一个(非虚拟)多重继承的相对简单的例子。看看下面的C++类层次结构。
注意Top被继承了两次(在Eiffel语言中这被称作重复继承)。这意味着类型Bottom的一个实例bottom将有两个叫做a的元素(分别为bottom.Left::a和bottom.Right::a)。 | ![]() Ley
|
Left、Right和Bottom在内存中是如何布局的?让我们先看一个简单的例子。Left和Right拥有如下的结构:
请注意第一个属性是从Top继承下来的。这意味着在下面两条语句后
如果我们提升Bottom指针,会发生什么事呢?
这段代码工作正常。我们可以把一个Bottom的对象当作一个Left对象来使用,因为两个类的内存部局是一样的。那么,如果将其提升为Right呢?会发生什么事?
| ![]() Aaron_Lim
|
虚拟继承为了避免重复继承Top,我们必须虚拟继承Top:
虽然从程序员的角度看,这也许更加的明显和简便,但从编译器的角度看,这就变得非常的复杂。重新考虑下Bottom的布局,其中的一个(也许没有)可能是: Bottom Left::Top::a Left::b Right::c Bottom::d | ![]() Ley
|
这个布局的优点是,布局的第一部分与Left的布局重叠了,这样我们就可以很容易的通过一个Left指针访问 Bottom类。可是我们怎么处理
我们将哪个地址赋给right呢? 经过这个赋值,如果right是指向一个普通的Right对象,我们应该就能使用 right了。但是这是不可能的!Right本身的内存布局是完全不同的,这样我们就无法像访问一个"真正的"Right对象一样,来访问升级的Bottom对象。而且,也没有其它(简单的)可以正常运作的Bottom布局。 解决办法是复杂的。我们先给出解决方案,之后再来解释它。
你应该注意到了这个图中的两个地方。第一,字段的顺序是完全不同的(事实上,差不多是相反的)。第二,有几个vptr指针。这些属性是由编译器根据需要自动插入的(使用虚拟继承,或者使用虚拟函数的时候)。编译器也在构造器中插入了代码,来初始化这些指针。 | ![]() super0555
|
vptr (virtual pointers)指向一个 “虚拟表”。类的每个虚拟基类都有一个vptr指针。要想知道这个虚拟表 (vtable)是怎样运用的,看看下面的C++ 代码。
第二个赋值使left指向了bottom的所在地址(即,它指向了Bottom对象的“顶部”)。我们想想最后一条赋值语句的编译情况(稍微简化了):
用语言来描述的话,就是我们用left指向虚拟表,并且由它获得了“虚拟基类偏移”(vbase)。这个偏移之后就加到了left,然后left就用来指向Bottom对象的Top部分。从这张图你可以看到Left的虚拟基类偏移是20;如果假设Bottom中的所有字段都是4个字节,那么给left加上20字节将会确实指向a字段。 | ![]() super0555
|
经过这个设置,我们就可以同样的方法访问Right部分。按这样
1
2
|
Bottom* bottom = <b>
new
</b> Bottom();
Right* right = bottom; <b>
int
</b> p = right->a;
|
之后right将指向Bottom对象的合适的部位:
Bottom | |
---|---|
vptr.Left | |
Left::b | |
right![]() | vptr.Right |
Right::c | |
Bottom::d | |
Top::a |
对top的赋值现在可以编译成像前面Left同样的方式。唯一的不同就是现在的vptr是指向了虚拟表的不同部位:取得的虚拟表偏移是12,这完全正确(确定!)。我们可以将其图示概括:
当然,这个例子的目的就是要像访问真正Right对象一样访问升级的Bottom对象。因此,我们必须也要给Right(和Left)布局引入vptrs:
现在我们就可以通过一个Right指针,一点也不费事的访问Bottom对象了。不过,这是付出了相当大的代价:我们要引入虚拟表,类需要扩展一个或更多个虚拟指针,对一个对象的一个简单属性的查询现在需要两次间接的通过虚拟表(即使编译器某种程度上可以减小这个代价)。