实例解析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个字节。
原文地址:https://www.cnblogs.com/ldlchina/p/4482211.html