C++虚表

来源:互联网 发布:淘宝我的五星好评截图 编辑:程序博客网 时间:2024/06/07 17:11

本文转载自: http://blog.csdn.net/jenaeli/article/details/60887267

在博客多态&虚函数中主要对多态的一些基本概念和虚函数做了介绍,下面,我们来探究一下【虚表】。

含有虚函数的类

  • 先来看看含有虚函数的类的大小吧!
class B{public:    virtual void Show()    {        cout << _b << endl;    }public:    int _b;};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

一眼看过去,这个类中只有一个int类型的变量_b,那么他的大小是不是只有4个字节呢?我运行了之后发现并不是,它的大小是8个字节。运行结果我这就不看了,下面我们用内存和监视来分析一下对象b的对象模型。

这里写图片描述

可以看到,当执行完b._b = 1; 时,对象b的成员_b与对象b的地址偏移了4个字节。而这4个字节里存放的是一个地址,我们把这个地址叫做虚表指针,它指向一个存放虚函数地址的内存块。这个内存块就是虚表。

由此,我们可得到b的对象模型

这里写图片描述

有虚函数的类相比普通类多了4个字节,用来存放虚表指针。在对象模型中,类中的成员变量存放在虚表指针之后。

  • 当类中有多个对象时,这些对象共用同一虚表。(可同时创建两个对象,在内存里查看其虚表指针是否相同来验证)

  • 当类中有多个成员变量时,对象模型中各变量的存放顺序按其在类中的声明顺序存放。

  • 当类中有多个虚函数时,虚表中各虚函数存放顺序按照其在类中的声明顺序存放。

直接来看例子吧:

class B{public:    virtual void Fun3()    {}    virtual void Fun1()    {}    virtual void Fun2()    {}public:    int _b3;    int _b1;    int _b2;};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

下图是我根据监视内存画出来的b的存储结构

这里写图片描述

再来看看它的对象模型是怎样的?

这里写图片描述

  • 需要注意一点
  • 类的构造函数在这里起填充虚表指针的作用

下面我们来看一下继承(参见博客【继承】)体系中虚函数的结构如何?

单继承中的虚函数

  • 虚函数无覆盖
class B{public:    B()        :_b(1)    {}    virtual void Fun1()    {        cout << "B::Fun1()";    }    virtual void Fun2()    {        cout << "B::Fun2()";    }    virtual void Fun3()    {        cout << "B::Fun3()";    }public:    int _b;};class D :public B{public:    D()        :_d(2)    {}    virtual void Fun4()    {        cout << "D::Fun4()";    }    virtual void Fun5()    {        cout << "D::Fun5()";    }    virtual void Fun6()    {        cout << "D::Fun6()";    }public:    int _d;};typedef void(*FUN_TEST)();void FunTest(){    B b;    cout<<sizeof(b)<<endl;    cout << "B vfptr:" << endl;    for (int iIdx = 0; iIdx < 3; ++iIdx)    {        FUN_TEST funTest = (FUN_TEST)(*((int*)*(int *)&b + iIdx)); funTest();        cout << ": " << (int *)funTest << endl;    }    cout << endl;    D d;    cout << sizeof(d) << endl;    cout << "D vfptr:" << endl;    for (int iIdx = 0; iIdx < 6; ++iIdx)    {        FUN_TEST funTest = (FUN_TEST)(*((int*)*(int *)&d + iIdx));        funTest();        cout << ": " << (int *)funTest << endl;    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65

其中FunTest()函数用于打印各对象中的函数。

先来看看结果吧

这里写图片描述

d中有两个成员变量以及一个虚表指针,所以大小为12.

这里写图片描述

可以发现,在派生类中,也完全继承了基类的虚函数,且遵循继承的存储结构。 
派生类中继承的基类的虚函数地址与基类中的相同。

  • 虚函数有覆盖

同样拿上面例子来说,我把派生类中的函数名Fun5改为Fun3,把Fun6改为Fun2,并且派生类中的循环次数改为4,其余保持不变。再次运行代码得到如下结果:

这里写图片描述

基类无变化,但是在派生类中,只打印了派生类的函数Fun2和Fun3.但是我的定义顺序明明是Fun3在Fun2前面的呀。怎么打印结果却是反的?这是怎么一回事呢?

这里写图片描述

当对象d被创建的时候,其虚表指针已经形成,但此时,虚表中存放的是从基类继承来的虚函数,当系统检测到派生类中已经对基类的虚函数进行重写的函数时,就拿该函数去替换虚表中基类对应的虚函数。所以虽然,Fun3定义在Fun2之前,但是替换时,系统从基类的Fun1开始,依次向下检测,当检测到Fun2被重写时,直接拿派生类中的Fun2去替换当前位置上的Fun2。如下图:

这里写图片描述

这里写图片描述

由此,可得出单继承的对象模型:

这里写图片描述

虚表的形成:

这里写图片描述

菱形继承

前面在继承中,为了解决二义性问题,我们引入了虚拟继承。在虚拟继承中,派生类中前4字节是偏移量的地址。但引入虚函数之后,我们看到虚表指针也存放于派生类的前4字节。那么,我们来看看,在菱形继承中,派生类的对象模型如何?

  • 菱形继承
class B{public:    virtual void FunTest1()    {        cout << "B::FunTest1()" << endl;    }    int _b;};class C1 :public B{public:    void FunTest1()    {        cout << "C1::FunTest1()" << endl;    }    virtual void FunTest2()    {        cout << "C1::FunTest2()" << endl;    }    int _c1;};class C2 :public B{public:    virtual void FunTest1()    {        cout << "C2::FunTest1()" << endl;    }    virtual void FunTest3()    {        cout << "C2::FunTest3()" << endl;    }    int _c2;};class D :public C1, public C2{public:    virtual void FunTest1()    {        cout << "D::FunTest1()" << endl;    }    virtual void FunTest2()    {        cout << "D::FunTest2()" << endl;    }    virtual void FunTest3()    {        cout << "D::FunTest3()" << endl;    }    virtual void FunTest4()    {        cout << "D::FunTest4()" << endl;    }    int _d;};typedef void(*Fun)();void Printvpf(){    D d;    cout << sizeof(d) << endl;    d.C1::_b = 1;    d.C2::_b = 2;    d._c1 = 3;    d._c2 = 4;    d._d = 5;    C1& c1 = d;    int* vpfAddr = (int*)*(int*)&c1;    Fun* pfun = (Fun*)vpfAddr;    while (*pfun)    {        (*pfun)();        pfun = (Fun*)++vpfAddr;    }    cout << endl;    C2& c2 = d;    vpfAddr = (int*)*(int*)&c2;    pfun = (Fun*)vpfAddr;    while (*pfun)    {        (*pfun)();        pfun = (Fun*)++vpfAddr;    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100

来看看结果吧!

这里写图片描述

结果可见,系统将派生类自己的虚函数放在了其第一个基类C1后面,并且派生类中重写的虚函数覆盖了基类的虚函数。

其对象模型如下:

这里写图片描述

  • 菱形虚拟继承
class B{public:    virtual void FunTest1()    {        cout << "B::FunTest1()" << endl;    }    int _b;};class C1 :virtual public B{public:    void FunTest1()    {        cout << "C1::FunTest1()" << endl;    }    virtual void FunTest2()    {        cout << "C1::FunTest2()" << endl;    }    int _c1;};class C2 :virtual public B{public:    virtual void FunTest1()    {        cout << "C2::FunTest1()" << endl;    }    virtual void FunTest3()    {        cout << "C2::FunTest3()" << endl;    }    int _c2;};class D :public C1, public C2{public:    virtual void FunTest1()    {        cout << "D::FunTest1()" << endl;    }    virtual void FunTest2()    {        cout << "D::FunTest2()" << endl;    }    virtual void FunTest3()    {        cout << "D::FunTest3()" << endl;    }    virtual void FunTest4()    {        cout << "D::FunTest4()" << endl;    }    int _d;};typedef void(*Fun)();void Printvpf(){    D d;    cout << sizeof(d) << endl;    d._b = 1;    d._c1 = 2;    d._c2 = 3;    d._d = 4;    C1& c1 = d;    int* vpfAddr = (int*)*(int*)&c1;    Fun* pfun = (Fun*)vpfAddr;    while (*pfun)    {        (*pfun)();        pfun = (Fun*)++vpfAddr;    }    cout << endl;    C2& c2 = d;    vpfAddr = (int*)*(int*)&c2;    pfun = (Fun*)vpfAddr;    while (*pfun)    {        (*pfun)();        pfun = (Fun*)++vpfAddr;    }    B& b = d;    vpfAddr = (int*)*(int*)&b;    pfun = (Fun*)vpfAddr;    while (*pfun)    {        (*pfun)();        pfun = (Fun*)++vpfAddr;    }}int main(){    Printvpf();    return 0;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112

结果如图:

这里写图片描述

表示偏移量的地址紧随虚表指针之后,其次才是成员变量。各类成员存放遵循继承规则。 
上图中表示偏移量的第一个数可能有人无法理解为什么几乎是一串f,其实它是负数在内存中的存储形式。

由此可得对象模型为:

这里写图片描述

原创粉丝点击