• 实例解析C++虚表


    OS:Windows 7

    关键字:VS2015,C++,V-Table,虚表,虚函数。

    C++ 了解的人都应该知道虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的。简称为V-Table。在这个表中,主是要一个类的虚函数的地址表,这张表解决了继承、覆盖的问题,保证其容真实反应实际的函数。这样,在有虚函数的类的实例中这个表被分配在了这个实例的内存中,所以,当我们用父类的指针来操作一个子类的时候,这张虚函数表就显得由为重要了,它就像一个地图一样,指明了实际所应该调用的函数。下面举例解析虚表。

    单一继承情况:

    #include "stdafx.h"
    #include <string>
    #include <iostream>
    
    using namespace std;
    
    class Base
    {
    public:
        virtual std::string Name();
    };
    
    std::string Base::Name()
    {
        return "Base";
    }
    
    class A : public Base
    {
    public:
        std::string Name() override;
        virtual std::string AName();
    };
    
    std::string A::Name()
    {
        return AName();
    }
    
    std::string A::AName()
    {
        return "A";
    }
    
    int main()
    {
        cout << "Size of Base: " << sizeof(Base) << endl;
        cout << "Size of A: " << sizeof(A) << endl;
    
        Base* pBase = new Base();
        cout << pBase->Name() << endl;
        A* pA = new A();
        cout << pA->Name() << endl;
        Base* pBaseA = pA;
        cout << pBaseA->Name() << endl;
    
        return 0;
    }

    控制台输出:

    x86:

    Size of Base: 4
    Size of A: 4
    Base
    A
    A

    x64:

    Size of Base: 8
    Size of A: 8
    Base
    A
    A

    虚表结构:

    总结:

    • Base和A对象的大小都是一个虚表指针的大小。一个指针大小在x86 (32bit)程序里是4个字节,在x64(64bit)程序里面是8个字节。
    • Watch窗口没有显示虚表里面的AName,在Watch立面添加“(*((Base*)pA)).__vfptr[1]”可以看到。

    多重继承情况:

    #include "stdafx.h"
    #include <string>
    #include <iostream>
    
    using namespace std;
    
    class A
    {
    public:
        virtual string AName() { return "A"; }
    };
    
    class B
    {
    public:
        virtual string BName() { return "B"; }
    };
    
    class C : public A, public B
    {
    public:
        virtual string BName() { return "C"; }
    };
    
    int main(int argc, _TCHAR* argv[])
    {
        cout << "Size of A: " << sizeof(A) << endl;
        cout << "Size of B: " << sizeof(B) << endl;
        cout << "Size of C: " << sizeof(C) << endl;
        C* pC = new C();
        C* pC1 = new C();
        cout << pC->BName() << endl;
        B* pB = static_cast<B*>(pC);
        cout << pB->BName() << endl;
        //A* pA = static_cast<A*>(pB);
        A* pA = dynamic_cast<A*>(pB);
        cout << pA->AName() << endl;
        A* pA1 = reinterpret_cast<A*>(pB);
        cout << pA1->AName() << endl;
        return 0;
    }

    控制台输出:

    x86:

    Size of A: 4
    Size of B: 4
    Size of C: 8
    C
    C
    A
    C

    x64:

    Size of A: 8
    Size of B: 8
    Size of C: 16
    C
    C
    A
    C

    看到这个结果有没有让你奇怪的地方?为什么“pA1->AName()”输出是C?

    虚表结构:

    总结:

    • pC和pC1是类C的两个实例指针,这两个实例的虚表是一样的,也就是说虚表是属于类的,一个类有一个虚表。
    • 因为类C重写了类B的BName函数,所以C的虚表里存的是C::BName
    • “B* pB = static_cast<B*>(pC);”,父类的指针来操作一个子类,虚函数实现了多态。
    • “A* pA = static_cast<A*>(pB);”是编译不通过的,因为A和B是不相关的两个类,也就是没有继承关系。
    • “A* pA = dynamic_cast<A*>(pB);”是可以的,因为dynamic_cast会在运行时进行类型检查,dynamic_cast是最安全的,但是效率最低。
    • “A* pA1 = reinterpret_cast<A*>(pB);”是可以编译通过了,但是运行时会有意想不到的结果。reinterpret_cast进行了强制类型转换,但是不会纠正虚表。
    • 派生类C有两个虚表指针分别指向两个虚表。

    虚拟继承情况:

    #include "stdafx.h"
    #include <string>
    #include <iostream>
    
    using namespace std;
    
    class Base
    {
    public:
        virtual string Name();
    };
    
    string Base::Name()
    {
        return "Base";
    }
    
    class A : virtual public Base
    {
    public:
        string Name() override;
        virtual string AName();
    };
    
    string A::Name()
    {
        return AName();
        return "A";
    }
    
    string A::AName()
    {
        return "A";
    }
    
    int main()
    {
        cout << "Size of Base: " << sizeof(Base) << endl;
        cout << "Size of A: " << sizeof(A) << endl;
    
        Base* pBase = new Base();
        cout << pBase->Name() << endl;
        A* pA = new A();
        cout << pA->Name() << endl;
        Base* pBaseA = pA;
        cout << pBaseA->Name() << endl;
    
        return 0;
    }

    控制台输出:

    x86:

    Size of Base: 4
    Size of A: 12
    Base
    A
    A

    x64:

    Size of Base: 8
    Size of A: 24
    Base
    A
    A

    虚表结构:

    总结:

    • 以x64为例,相比非虚单继承,A的大小打了16个字节,是因为A多了一个虚表指针指向用于存放A的虚函数AName的虚表,另外虚继承本身占了8个字节。
  • 相关阅读:
    Android天线信号刷新流程
    source insight配置
    android light sensor port
    wifi workflow
    vi/vim常用配置及使用命令
    contacts里QuickContactBadge弹出窗口
    ubuntu12.04与windows系统共享
    shell脚本编程
    凤凰照片的大小修改
    wf2
  • 原文地址:https://www.cnblogs.com/ldlchina/p/4482211.html
Copyright © 2020-2023  润新知