多态之虚表剖析1

来源:互联网 发布:钱永健 知乎 编辑:程序博客网 时间:2024/06/08 15:26

对于有虚函数的类,编译器都会维护一张虚表,对象的前四个字节就是指向虚表的指针

如下面代码:

#include <iostream>using namespace std;class B{public:    B()    {        _b = 10;        cout << "this = " << this << endl;    }    virtual void test1()    {        cout << "B::test1()" <<endl;    }    virtual void test2()    {        cout << "B::test2()" << endl;    }    virtual void test3()    {        cout << "B::test3()" << endl;    }private:    int _b;};int main(){    B b;    return 0;}

通过对地址进行跟踪:

这里写图片描述

在vs2013中虚函数地址以全0结束;

既然,只要类中声明虚函数,在创建对象时就会有一个指针指向此类的虚表位置,那么在类的继承体系中,基类中的虚表是如何被继承的呢?

而且,虚表中存放虚函数的地址,那么我们是否可以用一个函数指针不同类去调用它呢?

因此,为了验证虚表中存放的确实是虚函数的地址,我们不用类去调用,用一个函数指针来调用:

下面代码有三个虚函数,分别输出不同结果:

#include <iostream>using namespace std;class B{public:    B()    {        _b = 10;        //cout << "this = " << this << endl;    }    virtual void test1()    {        cout << "B::test1()" <<endl;    }    virtual void test2()    {        cout << "B::test2()" << endl;    }    virtual void test3()    {        cout << "B::test3()" << endl;    }private:    int _b;};typedef void(*FUN_TEST)();int main(){    B b;    for (int i = 0; i < 3; ++i)//类中声明了三个虚函数    {        /*我们取对象b的地址,b的地址此时指向b的存储空间,        我们已经知道对象的前四个字节存放的是对象中虚表的            指针,将对象的前四个字节强转成int*,即就可将指向            对象的指针转变成指向对象中虚表的地址,再将它强转成            指向函数的函数指针,就可找到虚函数的地址*/        FUN_TEST fun = (FUN_TEST)(*((int*)*(int*)&b + i));        fun();        cout << ":" << (int*)fun << endl;    }    system("pause");    return 0;}

这里写图片描述

结果:

这里写图片描述

我们已经知道,在类中只要有虚函数,编译器就会为此类提供一个保存虚函数地址的虚表,那么,在类的继承体系中,这个虚表是如何被继承下来的呢???

1、没有覆盖:

我们用上面同样的方法,用函数指针来调用派生类中的虚函数

class B{public:    B()    {        _b = 10;    }    virtual void test1()    {        cout << "B::test1()" <<endl;    }    virtual void test2()    {        cout << "B::test2()" << endl;    }    virtual void test3()    {        cout << "B::test3()" << endl;    }private:    int _b;};class D :public B{public:    virtual void test4()    {        cout << "B::test4()" << endl;    }    virtual void test5()    {        cout << "B::test5()" << endl;    }    virtual void test6()    {        cout << "B::test6()" << endl;    }private:    int _d;};typedef void(*FUN_TEST)();int main(){    D d;    cout << sizeof(d) << endl;    for (int i = 0; i < 6; ++i)    {        FUN_TEST fun = (FUN_TEST)(*((int*)*(int*)&d + i));        fun();        cout << ":" << (int*)fun << endl;    }    system("pause");    return 0;}

结果:

这里写图片描述

分析:在计算派生类大小为12,而非16。说明在继承时基类和派生类的两个虚表合并了,而输出结果也是先输出基类的虚函数,再输出派生类的虚函数,即在新合并的派生类虚表中对应的模型应该是下面这种:

这里写图片描述

总结:

1、虚函数按照其声明顺序存在于虚表中

2、在派生类中,前面是基类的虚函数,后面是派生类的虚函数。

2、有覆盖

即在派生类中将基类的虚函数进行重写。

前面代码稍加改造:

class B{public:    B()    {        _b = 10;    }    virtual void test1()    {        cout << "B::test1()" <<endl;    }    virtual void test2()    {        cout << "B::test2()" << endl;    }    virtual void test3()    {        cout << "B::test3()" << endl;    }private:    int _b;};class D :public B{public:    void test1()    {        cout << "D::test1()" << endl;    }    virtual void test5()    {        cout << "D::test5()" << endl;    }    virtual void test6()    {        cout << "D::test6()" << endl;    }private:    int _d;};typedef void(*FUN_TEST)();int main(){    D d;    cout << sizeof(d) << endl;    for (int i = 0; i < 5; ++i)    {        FUN_TEST fun = (FUN_TEST)(*((int*)*(int*)&d + i));        fun();        cout << ":" << (int*)fun << endl;    }    system("pause");    return 0;}

结果:

这里写图片描述

分析不难得到:派生类的虚表生成是先拷贝基类的虚函数表;如果派生类重写了基类的某个虚函数,就替换虚表中同位置的基类虚函数。然后跟上自己定义的虚函数。

0 0
原创粉丝点击