C++虚指针和虚函数表
C++虚指针和虚函数表
C++实现多态的方式是通过虚指针和虚函数表
具体来说,当访问一个虚函数时,通过访问vptr,再加上一个偏移获取函数的指针。
子类的vtable相当于父类vtable的拷贝,然后替换掉表中被重写的虚函数的地址为子类的该函数地址。

代码
#include<iostream>
#include<vector>
class A
{
public:
A(){
Print();
}
virtual void FuncA(){}
virtual void Print()
{
uint64_t* vptr = (uint64_t*)(*((uint64_t*)this)); // 取虚指针
std::cout << "A::vptr: " << vptr << std::endl; // 输出虚指针
std::cout << "A::FuncA: " << (void*)(&A::FuncA) << std::endl; // 输出A::FuncA地址
std::cout << "A::Print: " << (void*)(&A::Print) << std::endl; // 输出A::Print地址
std::cout << "A::vtable[0]: " << (void*)(vptr[0]) << std::endl; // 输出虚函数表第一个函数的地址
std::cout << "A::vtable[1]: " << (void*)(vptr[1]) << std::endl; // 输出虚函数表第二个函数的地址
}
};
class B: public A
{
public:
B():A(){
Print();
}
virtual void Print()
{
uint64_t* vptr = (uint64_t*)(*((uint64_t*)this));
std::cout << "B::vptr: " << vptr << std::endl;
std::cout << "B::FuncA: " << (void*)(&B::FuncA) << std::endl;
std::cout << "B::Print: " << (void*)(&B::Print) << std::endl;
std::cout << "B::vtable[0]: " << (void*)(vptr[0]) << std::endl;
std::cout << "B::vtable[1]: " << (void*)(vptr[1]) << std::endl;
}
};
int main()
{
B b;
return 0;
}输出结果
A::vptr: 0x4020d8
A::FuncA: 0x40121c
A::Print: 0x401228
A::vtable[0]: 0x40121c
A::vtable[1]: 0x401228
B::vptr: 0x4020b8
B::FuncA: 0x40121c
B::Print: 0x40135a
B::vtable[0]: 0x40121c
B::vtable[1]: 0x40135a子类执行父类构造函数时能正确调用父类的虚函数
A::vptr: 0x4020d8
B::vptr: 0x4020b8
先执行A的构造函数,此时虚指针会被赋值为A类的虚指针,调用的为父类的虚函数
再执行B的构造函数,此时虚指针会被赋值为B类的虚指针,调用的为子类重写的虚函数
即使重写了父类的虚函数,在执行父类构造函数时调用虚函数,调用的为父类的虚函数而不是子类。保证虚函数能够正确被调用
单继承情况下,vptr验证
A::vptr: 0x4020d8
B::vptr: 0x4020b8
头个8字节为虚指针,子类的虚指针不同于父类的虚指针
A::FuncA: 0x401228
A::Print: 0x401234
A::vtable[0]: 0x401228
A::vtable[1]: 0x401234B::FuncA: 0x401228
B::Print: 0x401366
B::vtable[0]: 0x401228
B::vtable[1]: 0x401366
虚指针指向vtable,函数地址与定义顺序一致
从虚函数地址表可以看出,没被重写的方法函数地址没有变化,被重写的函数地址发生了变化




