虚继承与虚函数继承

来源:互联网 发布:微信java通用版下载 编辑:程序博客网 时间:2024/05/21 09:40

1、对虚继承层次的对象的内存布局,在不同编译器实现有所区别。

首先,说说GCC的编译器.
它实现比较简单,不管是否虚继承,GCC都是将虚表指针在整个继承关系中共享的不共享的是指向虚基类的指针

class A {    int a;    virtual ~A(){}};class B:virtual public A{    virtual void myfunB(){}};class C:virtual public A{    virtual void myfunC(){}};class D:public B,public C{    virtual void myfunD(){}};

以上代码中sizeof(A)=8,sizeof(B)=12,sizeof(C)=12,sizeof(D)=16.
解释:A中int+虚表指针。B,C中由于是虚继承因此大小为A+指向虚基类的指针,B,C虽然加入了自己的虚函数,但是虚表指针是和基类共享的,因此不会有自己的虚表指针。D由于B,C都是虚继承,因此D只包含一个A的副本,于是D大小就等于A+B中的指向虚基类的指针+C中的指向虚基类的指针。

如果B,C不是虚继承,而是普通继承的话,那么A,B,C的大小都是8(没有指向虚基类的指针了),而D由于不是虚继承,因此包含两个A副本,大小为16.注意此时虽然D的大小和虚继承一样,但是内存布局却不同。

然后,来看看VC的编译器
vc对虚表指针的处理比GCC复杂,它根据是否为虚继承来判断是否在继承关系中共享虚表指针,而对指向虚基类的指针和GCC一样是不共享,当然也不可能共享。
代码同上。
运行结果将会是sizeof(A)=8,sizeof(B)=16,sizeof(C)=16,sizeof(D)=24.
解释:A中依然是int+虚表指针。B,C中由于是虚继承因此虚表指针不共享,由于B,C加入了自己的虚函数,所以B,C分别自己维护一个虚表指针,它指向自己的虚函数。(注意:只有子类有新的虚函数时,编译器才会在子类中添加虚表指针)因此B,C大小为A+自己的虚表指针+指向虚基类的指针。D由于B,C都是虚继承,因此D只包含一个A的副本,同时D是从B,C普通继承的,而不是虚继承的,因此没有自己的虚表指针。于是D大小就等于A+B的虚表指针+C的虚表指针+B中的指向虚基类的指针+C中的指向虚基类的指针。
同样,如果去掉虚继承,结果将和GCC结果一样,A,B,C都是8,D为16,原因就是VC的编译器对于非虚继承,父类和子类是共享虚表指针的。

2、虚函数继承

虚函数继承是解决多态性的,当用基类指针指向派生类对象的时候,基类指针调用虚函数的时候会自动调用派生类的虚函数,这就是多态性,也叫动态编联。

虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的。编译器必需要保证虚函数表的指针存在于对象实例中最前面的位置(这是为了保证正确取到虚函数的偏移量)。

假设我们有这样的一个类:

class Base {public:    virtual void f() { cout << "Base::f" << endl; }    virtual void g() { cout << "Base::g" << endl; }    virtual void h() { cout << "Base::h" << endl; }};

当我们定义一个这个类的实例,Base b时,其b中成员的存放如下:
这里写图片描述

指向虚函数表的指针在对象b的最前面
虚函数表的最后多加了一个结点,这是虚函数表的结束结点,就像字符串的结束符“\0”一样,其标志了虚函数表的结束。这个结束标志的值在不同的编译器下是不同的。在WinXP+VS2003下,这个值是NULL。而在Ubuntu 7.10 + Linux 2.6.22 + GCC 4.1.3下,这个值是如果1,表示还有下一个虚函数表,如果值是0,表示是最后一个虚函数表。
因为对象b中多了一个指向虚函数表的指针,而指针的sizeof是4,因此含有虚函数的类或实例最后的sizeof是实际的数据成员的sizeof加4。

下面将讨论针对基类含有虚函数的继承

(1)在派生类中不对基类的虚函数进行覆盖,同时派生类中还拥有自己的虚函数,比如有如下的派生类:

class Derived: public Base {public:    virtual void f1() { cout << "Derived::f1" << endl; }    virtual void g1() { cout << "Derived::g1" << endl; }    virtual void h1() { cout << "Derived::h1" << endl; }};

基类和派生类的关系如下:
这里写图片描述

当定义一个Derived的对象d后,其成员的存放如下:
这里写图片描述
可以发现:

      1)虚函数按照其声明顺序放于表中。      2)父类的虚函数在子类的虚函数前面。

此时基类和派生类的sizeof都是数据成员的sizeof加4。

(2)在派生类中对基类的虚函数进行覆盖,假设有如下的派生类:

class Derived: public Base {public:    virtual void f() { cout << "Derived::f" << endl; }    virtual void g1() { cout << "Derived::g1" << endl; }    virtual void h1() { cout << "Derived::h1" << endl; }};

基类和派生类之间的关系:其中基类的虚函数f在派生类中被覆盖了
这里写图片描述
当我们定义一个派生类对象d后,其d的成员存放为:
这里写图片描述

可以发现:

1)覆盖的f()函数被放到了虚表中原来父类虚函数的位置。2)没有被覆盖的函数依旧。

这样,我们就可以看到对于下面这样的程序,

Base *b = new Derive();b->f();

由b所指的内存中的虚函数表的f()的位置已经被Derive::f()函数地址所取代,于是在实际调用发生时,是Derive::f()被调用了。这就实现了多态

(3)多继承:无虚函数覆盖

假设基类和派生类之间有如下关系:
这里写图片描述

对于子类实例中的虚函数表,是下面这个样子:
这里写图片描述

我们可以看到:

1) 每个父类都有自己的虚表。2) 子类的成员函数被放到了第一个父类的表中。(所谓的第一个父类是按照声明顺序来判断的)

由于每个基类都需要一个指针来指向其虚函数表,因此d的sizeof等于d的数据成员加3*4=12。

(4)多重继承,含虚函数覆盖

假设,基类和派生类又如下关系:派生类中覆盖了基类的虚函数f
这里写图片描述

下面是对于子类实例中的虚函数表的图:
这里写图片描述

我们可以看见,三个父类虚函数表中的f()的位置被替换成了子类的函数指针。这样,我们就可以任一静态类型的父类来指向子类,并调用子类的f()了。如:

Derive d;Base1 *b1 = &d;Base2 *b2 = &d;Base3 *b3 = &d;b1->f(); //Derive::f()b2->f(); //Derive::f()b3->f(); //Derive::f()b1->g(); //Base1::g()b2->g(); //Base2::g()b3->g(); //Base3::g()

http://blog.chinaunix.net/uid-25132162-id-1564955.html
http://blog.csdn.net/gxiaob/article/details/10149069

0 0