您的位置:首页 > 编程语言 > C语言/C++

c++多重继承和虚继承及虚函数深入理解

2007-10-24 17:29 1716 查看

先看一例子:
class base
{
public:
virtual void f() const {};
};

class d1 : virtual public base
{};

class d2 : virtual public base
{};

class derived : public d1, public d2
{};
using namespace std;
int main()
{
d1 b;
d2 c;
derived d;
cout<<sizeof(b)<<endl;
cout<<sizeof(c)<<endl;
cout<<sizeof(d)<<endl;
return 0;
}

结果是 8, 8 12.
先说class Base吧,它有一个虚函数,且没有父类,所以只有一个指针大小,也就是4BYTES了.
至于d1和d2就不那么简单了,它不仅有虚函数而且是虚继承.要复杂一些了.
D1之所以为8是由于它有两个表指针组成的.
一个PVBTBL,一个是PVFTBL.
PVBTBL是指虚基类表指针,一个类可以有0-n个VBTBL(VIRTUAL BASE CLASS TABLE),在这里只有
一个,不过很快就会看到有两个VBTBL的类了。
PVFTBL是指虚函数表指针了,一个类可以有 0-N个VFTBL(VIRTUAL FUNCTION TABLE),在这里也只有一个。我们看一看D1的布局吧。
D1
---------------
PVBTBL ---------------------》指向一个虚基类表。
--------------
PVFTBL ---------------------》指向一个虚函数表。
--------------
所以其大小就为8了。
再说说其初始化过程。我们知道任何一个类的虚基类的构造函数是最先被调用。
最开如,编译器会把d1::vbtbl的指针表放在图中PVBTBL的地方,此时THIS指针也指向PVBTBL,
然后调整THIS指针令其指向PVFTBL,并调用BASE::BASE();
BASE()会在PVFTBL的地方放上OFFSET BASE::VFTBL,由于它没有数据成员要初始化,所以它就返回了。呵呵,很不幸,它很快就要被D1::的VFTBL所代替了,这也正是为什么C++会根据实际对象的类型所进行正确行为的原因。不过,这里的替代有点儿特别。
它先查看,VBTABL(其中第二项为虚基类的偏移[PVBTABL+4]),找到虚基类的偏移量,然后再加上THIS指针的值,些时它正好指向PVFTBL,它就用D1::VFTBL替代了虚基类的虚函数表了。

上边提到有的类会有两个虚基表,-DEPRIVED类就是这种情况了。它的初始化比上面所说的更复杂。
先看布局也再说吧
----------
PVBTBL ------》同上
----------
PVBTBL ------》同上
----------
PVFTBL--------》同上
----------
PVFTBL--------》同上。
---------------------
很明显可以看到它有两个虚基类表,ONE FOR D1,ANOTHER FOR D2。
虚基类表的个数取决于当前的继承方式及其基类的情况。
在这里可以看到,它的基类D1,D2都有虚继承的情况,所以这里就有两个虚基类表了。
INIT:
在这里编译器会先准备两个VBTBL,然后,当初始化时(也就是调用CONSTRUCTOR时),两个VBTBL分别放到相应的位置,然后调整THIS指针的值,THIS+8,POINTING TO THE FIRST PVFTBL的位置,然后调用BASE::BASE(),可以看到的确是虚基类的构造函数是最先调用的。
它会把它的OFFSET BASE::VFTBL,放到this + 8 的位置,没有其它要初始化的成员,SO,RETURN TO THE CALLER。
THEN THIS= THIS -8,INVOKE THE D1::D1(),
ENTER THE D1::D1(),FIRSTLY ,IT LOOKS UP THE VBTBL POINTED BY FIRST PVTBL FOR THE OFFSET THE ITSELF,,然后,修改THIS的值,令其指向,SUBOBJECT OF D1 ,由于这里也没有成员,所以初始化也很简单,它只是用它自己的虚函数替代上面有BASE::BASE()所放置的虚函数表,然后,返回。至于后面的D2,也和这里类似,不过THIS指针所指的位置不同,它指向上图中的最后一个位置。
终于到最后一步了,DEPRIVED会用它自己的虚函数表去替代原来已有的。。。。。(反正自己看,不用说太清了。)
下面还有更好玩的。一个类可能会有多个虚函数表,且不同的。
看这个例子。
#include "stdafx.h"
#include <string.h>
class Base1
{
public:
virtual void Base1Test() {printf("this is from Base1 virtual function /n");}
int a;

};

class Base2
{
public:
virtual void Base2Test() {printf("this is from the Base2 virtual function/n");}
int b;
};
class Deprived : public virtual Base1, public Base2
{
public:
int d;
};
Base1 * base1;
Base2 *base2;
void f()
{
Deprived deprived;
base1 = &deprived;
base2 = &deprived;
base1->Base1Test();
base2->Base2Test();
deprived.a = 16;
deprived.b = 32;
deprived.d = 48;
return;
}
int main(int argc, char* argv[])
{
printf("sizeof(Deprived):%d/n", sizeof(Deprived));
f();

return 0;
}
NOTE:本例中,只有一个VBTBL,呵呵,由于DEPRIVED VIRTURE INHERITS THE BASE1,
但是它却有多个虚函数表。ONE FOR BASE1, ANOTHER FOR BASE2。
它的内存布局和上面有较大变化了。如下所示。
DEPRIVE
----------------
BASE2 PARTY =========》 INCLUDE 一个虚函数表指针及int b(它就是BASE2的成员变量。
----------------
VBTBL PARTY ==========》就是一个指向虚基类表的指针了。不用多说了。
-----------------
DEPRIVE PARTY ==========》这里仅有一个DEPRIVED的成员变量d,它和BASE2共用一个虚函数表指针。所以就只有一个成员变量D了。
-----------------
VIRTUAL BASE PARTY -=======》也就是BASE1 PARTY了。它结构如下。
===========
PVFTBL --------------》BASE1的VIRTUAL FUNC TBL
=============
-------------------------------

理所当然它会所调用BASE1的CONSTRUCTOR,(FOR THE VIRTUAL BASE REASON。。。。),
,然后正常初始化,不过到了最后,会比较独特,DPRIVED CLASS有两个VIRTUAL TABLE。
一个是为BASE1准备的,一个是为BASE2准备的。
也正是为什么可以用基类的指针正确的调用子类的VIRTURL FUNCTION的原因。。。。。
更详细,就不说了,反正是自己看。呵呵。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: