参考文章 虚函数表概述
多态的实现细节
C++中的虚函数的作用主要是实现了多态的机制。关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数
#include<iostream>
using std::cout;
class Base
{
public:
Base() {
cout << "this is Base Create Func\n";
}
virtual ~Base() {
cout << "~Base()\n";
}
virtual void virtual_func() {
// 获取 vptr 的值
cout << "Base this: " << this << ", vptr: " << *(void**)this << "\n";
}
};
class A final : public Base
{
public:
void virtual_func() override {
// 获取 vptr 的值
cout << "A this: " << this << ", vptr: " << *(void**)this << "\n";
}
};
class B final : public Base
{
public:
void virtual_func() override {
// 获取 vptr 的值
cout << "B this: " << this << ", vptr: " << *(void**)this << "\n";
}
};
int main() {
auto base = new Base();
Base* a = new A();
Base* a1 = new A();
Base* b = new B();
base->virtual_func();
a->virtual_func();
a1->virtual_func();
b->virtual_func();
return 0;
}
// 运行结果
this is Base Create Func
this is Base Create Func
this is Base Create Func
this is Base Create Func
// 对象的地址 指向的虚函数表的地址
Base this: 00000185A07B64D0, vptr: 00007FF62700BC30 // base
A this: 00000185A07B6750, vptr: 00007FF62700BCC0 // a
A this: 00000185A07B5DA0, vptr: 00007FF62700BCC0 // a1 同一张虚函数表
B this: 00000185A07B62A0, vptr: 00007FF62700BD00 // b
从代码的运行结果可以看到,同一个类的不同对象共用同一张虚函数表,这是在编译期间就决定好的
内存布局
虚表指针(vptr
)的位置
本身就是一个指针 大小就是指针的大小
虚表指针(
vptr
)是一个隐藏的成员,通常存储在对象的内存中,一般位于对象的起始位置(具体位置由编译器决定)。通过
this
指针可以访问到虚表指针,但this
本身并不是vptr
的地址。
v table
虚函数表是指在每个包含虚函数的类中都存在着一个函数地址的数组。当我们用父类的指针来操作一个子类的时候,这张虚函数表指明了实际所应该调用的函数。
C++的编译器保证虚函数表的指针存在于对象实例中最前面的位置,这样通过对象实例的地址得到这张虚函数表,然后就可以遍历其中函数指针,并调用相应的函数。
单继承的情况下内存布局
没重写的情况:
虚函数按照其声明顺序放于表中。
父类的虚函数在子类的虚函数前面。
派生类覆盖基类虚函数
虚表中派生类覆盖的虚函数的地址被放在了基类相应的函数原来的位置
派生类没有覆盖的虚函数延用基类的
多继承下的虚函数表
基类和派生类概述:
A、B 和 C 是虚函数类,每个类都会有一个虚表指针(vptr)和一个对应的虚表(vtable)
D 是从 A、B 和 C 多继承的派生类,且覆盖了每个基类的虚函数,并新增了一个虚函数 funcD()
class A {
public:
virtual void funcA() {}
int a_member;
};
class B {
public:
virtual void funcB() {}
int b_member;
};
class C {
public:
virtual void funcC() {}
int c_member;
};
class D : public A, public B, public C {
public:
void funcA() override {} // 重写 A 的虚函数
void funcB() override {} // 重写 B 的虚函数
void funcC() override {} // 重写 C 的虚函数
virtual void funcD() {} // D 自己的虚函数
int d_member;
};
每个基类拥有自己的子对象,独立的虚表指针(
vptr
)用来维护从该基类继承的虚函数行为。多继承时,基类之间是“平等的”,并不是所有虚函数都由第一个基类的虚表管理。
如果重写了基类的虚函数:
钻石型\菱形继承问题
解决方法:
采用虚继承的方式:
class A {
public:
int data;
};
class B : virtual public A {};
class C : virtual public A {};
class D : public B, public C {};
B
和C
中没有直接包含A
的子对象,而是通过 虚基表 指向A
的唯一子对象。A
的子对象被存储在D
中,由B
和C
共享