Hi~!欢迎来到碧波空间,平时喜欢用博客记录学习的点滴,欢迎大家前来指正,欢迎欢迎~~
✨✨ 主页:碧波
📚 📚 专栏:C++ 系列文章![]()
📚 虚表(Virtual Table)和虚表指针(vptr)是C++中实现多态性的重要机制。它们通过动态绑定(或运行时多态性)实现了在基类指针或引用下调用派生类的虚函数。
目录
虚函数(Virtual Function)
虚函数是在基类中声明为 virtual 的成员函数。在派生类中可以根据需要进行重写(override)。它允许在运行时动态地确定调用哪个版本的函数,这是实现多态性的一种关键机制。
虚函数表(Virtual Table,VTABLE)
虚表是每个包含虚函数的类的静态成员之一,它存储了类的虚函数的地址。每个类有一个对应的虚表,其中的每个条目是一个指向相应虚函数的指针。当类被定义时,编译器会在其虚表中生成对应的虚函数条目。
虚表指针(vptr)
虚表指针是一个隐藏的指针,存在于每个包含虚函数的类的对象中。它指向该类的虚函数表(虚表)。当调用一个虚函数时,实际上是通过对象的虚表指针找到对应的虚表,然后根据函数的偏移量来调用正确的函数。
虚表和虚表指针工作原理
🎯 通过示意图来更好地理解虚表和虚表指针的概念,有 Base
类和 Derived
类:
Base 类对象内存布局:
+--------------+
| vptr (Base) | --> 指向 Base 类的虚表
+--------------+
| Base 类的数据 |
+--------------+
Derived 类对象内存布局:
+----------------+
| vptr (Derived) | --> 指向 Derived 类的虚表
+----------------+
| Derived 类的数据 |
+----------------+
每个对象的开头都有一个虚表指针。这个指针指向其对应的类的虚表。例如,Base
类对象的虚表指针指向 Base
类的虚表,而 Derived
类对象的虚表指针则指向 Derived
类的虚表。
🎯 通过简单的示例代码,演示如何利用虚表和虚表指针实现动态多态性;
class Base {
public:
virtual void func1() {
std::cout << "Base::func1()" << std::endl;
}
virtual void func2() {
std::cout << "Base::func2()" << std::endl;
}
};
class Derived : public Base {
public:
void func1() override {
std::cout << "Derived::func1()" << std::endl;
}
};
int main() {
Base* basePtr;
Derived derivedObj;
basePtr = &derivedObj;
// 虚函数的调用
basePtr->func1(); // 输出 "Derived::func1()"
basePtr->func2(); // 输出 "Base::func2()"
return 0;
}
当编译器在编译的时候会分别为类Base和类Derived 创建属于各自类的虚函数表。并Derived 当创建父类指针指向对象的时候,会初始化vptr指针,指向该对象所属类的虚函数表。
通过指向子类对象的父类指针,调用成员函数时,会先判断是否为virtual 虚函数,如果是虚函数的话,会通过vptr访问对应的类虚函数表中所存放的成员函数指针。依次实现多态。
🎯 结合示例,总结工作过程。
虚函数声明与重写:
在基类中,如果一个函数被声明为虚函数(使用 virtual
关键字),它可以被派生类重写。
虚表的构建:
每个类的虚函数表在编译时构建。虚表中的每个条目存储了对应虚函数的地址,这些地址是相对于类的基址的偏移量。
虚表指针的初始化与使用:
当创建一个对象时,如果该对象所属的类有虚函数,编译器会在对象的内存布局中插入一个虚表指针。这个指针被初始化为指向该类的虚表的地址。
动态绑定:
当调用一个虚函数时,实际被调用的函数取决于对象的实际类型(而不是引用或指针的静态类型)。这是因为编译器会通过对象的虚表指针来查找对应的虚表,然后根据虚函数的偏移量来调用正确的函数实现。
🔥 虚表和虚表指针区别:
虚表属于类,虚表指针属于对象。
虚函数表:在编译阶段生成,编译器将类中 虚函数的地址存放在虚函数表中,虚函数
表存在于全局数据区.data,每个类仅有一个,供所有对象共享。
虚表指针:指向虚函数表首地址的一个指针,存在于每个基类对象的内存中,在调用构
造函数构造对象时,设置虚表指针__vfptr