继承----有关虚函数和虚拟继承

来源:互联网 发布:jsp登录页面数据库 编辑:程序博客网 时间:2024/05/16 07:57

可以参考以下文章:

http://blog.csdn.net/haoel/archive/2007/12/18/1948051.aspx  陈皓 C++ 虚函数表解析

http://blog.csdn.net/haoel/archive/2008/10/15/3081328.aspx  陈皓 C++ 对象的内存布局(上)

http://blog.csdn.net/haoel/archive/2008/10/15/3081385.aspx  陈皓 C++ 对象的内存布局(下)

 

http://www.cnblogs.com/cswuyg/archive/2010/08/20/1804113.html  C++对象内存布局测试总结

 

这些文章的陈述和之前自己的理解还是比较吻合的。

但有一些细节需要注意一下,写在下面加深一下记忆。

(1)单一的一般继承(带成员变量、虚函数、虚函数覆盖)

 

     1)虚函数表在最前面的位置。

     2)成员变量根据其继承和声明顺序依次放在后面。

     3)在单一的继承中,overwrite的虚函数在虚函数表中得到了更新

 

(2)多重继承(带成员变量、虚函数、虚函数覆盖)

     1)  每个父类都有自己的虚表。

     2)  子类的成员函数被放到了第一个父类的表中。

     3)  内存布局中,其父类布局依次按声明顺序排列。

     4)  每个父类的虚表中的f()函数都被overwrite成了子类的f()。这样做就是为了解决不同的父类类型的指针指向同一个子类实例,而能够调用到实际的函数。


(3)重复多重继承(带成员变量、虚函数、虚函数覆盖)

   这个和上面一样,只不过最顶层的基类在二级子类里有两份同样的拷贝,在直接访问顶层基类的 成员变量时,会出现二义性。

d.ib = 0;             //二义性错误

d.B1::ib = 1;           //正确

          d.B2::ib = 2;           //正确


(4)单一的虚拟继承(带成员变量、虚函数、虚函数覆盖)

(5)钻石型的虚拟多重继承(带成员变量、虚函数、虚函数覆盖)

 

        第4和第5种情况就比较复杂了,因为这个内存布局其实是和具体的编译器相关的,不同的编译器实现不一样。

        我们可以参照教材《inside c++ object model》P121和P123中的介绍。分别是使用pointer strategy和virtual table offset strategy.

        在单一虚拟继承时:

      (1)VS编译器:无论有无虚函数,必然含有虚基类表指针。虚基类表中的内容为本类实例的偏移和基类实例的相对偏移值。如果有虚函数,那么基类的虚函数表跟派生类的虚函数表是分开的。

      (2)GNU的GCC编译器:跟VS的编译器类似,有不同的地方是,虚基类表跟派生类的虚函数表合并。另外通过虚基类表指针往正负两个方向寻址,可以获得不同偏移值,也就是说有两个功能一样的虚函数表

 

       最后总结一下虚基类表的问题:

         VS编译器会去使用虚基类表,用于寻址虚基类地址(virtual base class table strategy)。而GCC编译器则没有这么做,而是直接在派生类对象地址上加上一个常数,获得虚基类实例的地址(virtual table offset strategy.)。

 

       有一个很好的例子:

 

#include <stdio.h>

class A
{
public:
    virtual int foo0(){
        return 0;
    }
    char a[3];
};

class B: virtual public  A
{
public:
    virtual int foo1(){
        return 1;
    }
    char b[3];
};

class C:virtual public  B
{
public:
    virtual int foo2(){
        return 2;
    }
    char c[3];
};

int main(int argc, char* argv[])
{
    A a;
    B b;
    C c;

    printf(" sizeof A=%d, sizeof B=%d, sizeof C=%d /n",sizeof (A), sizeof (B), sizeof (C));
    return 0;
}

 

在g++编译器里: sizeof A=8, sizeof B=16(A和B都有虚函数表,但B的虚函数表的负方面的地址里存着virtual base class(A)offset), sizeof C=24(C的虚函数表的负方面的地址里存着virtual base class(B)offset)

在VC++编译器里: sizeof A=8, sizeof B=20(A和B都有虚函数表,并且B还有一个virtual class base table pointer), sizeof C=32

 

 

 

原创粉丝点击