在C++中,虚函数(virtual functions)是一种支持多态(polymorphism)的机制。当一个基类中的成员函数被声明为虚函数时,它可以在派生类中被重写(override),并且通过基类指针或引用调用该函数时,实际执行的是派生类中的版本。为了实现这一机制,编译器会使用一种称为“虚函数表”(vtable)的数据结构。
虚函数表(vtable)
虚函数表是一个包含指向虚函数地址的指针数组。每个包含虚函数的类都有一个对应的虚函数表。当创建一个类的对象时,该对象会有一个隐藏的指针(通常称为vptr,即虚表指针),指向其所属类的虚函数表。这个vptr是隐式的,用户代码中不可见。
工作原理
- 类定义:当你定义一个包含虚函数的类时,编译器会为这个类生成一个虚函数表。
- 对象实例化:当你创建一个该类的对象时,对象内部会包含一个指向相应虚函数表的指针。
- 虚函数调用:当你通过基类指针或引用来调用虚函数时,程序会根据对象的实际类型,通过vptr找到正确的虚函数表,并从那里获取虚函数的地址来执行。
示例
下面是一个简单的示例,展示了虚函数和虚函数表的工作原理:
#include <iostream>
class Base {
public:
virtual void show() { // 基类中的虚函数
std::cout << "Base::show()" << std::endl;
}
virtual ~Base() {} // 虚析构函数
};
class Derived : public Base {
public:
void show() override { // 派生类中重写虚函数
std::cout << "Derived::show()" << std::endl;
}
};
int main() {
Base* basePtr = new Derived(); // 基类指针指向派生类对象
basePtr->show(); // 通过基类指针调用虚函数
delete basePtr; // 通过基类指针删除派生类对象,需要虚析构函数
return 0;
}
在这个例子中:
Base类有一个虚函数show()和一个虚析构函数~Base()。Derived类继承自Base并重写了show()函数。- 在
main函数中,我们创建了一个Derived对象,并使用Base类型的指针指向它。 - 当通过
basePtr调用show()时,由于show()是虚函数,实际调用的是Derived类中的show()版本。
虚函数表的内容
假设 Base 和 Derived 类的虚函数表如下所示:
-
Base的虚函数表可能包含:Base::showBase::~Base
-
Derived的虚函数表可能包含:Derived::show(覆盖了Base::show)Base::~Base(保持不变)
每个 Base 或 Derived 对象都会有一个指向其对应虚函数表的 vptr。
为了更好地理解类A、B和C的虚函数表结构,我们可以绘制一个简单的结构图来表示。假设我们有如下定义:
class A {
public:
virtual void funcA() { /* ... */ }
virtual ~A() {} // 确保A是多态的
};
class B {
public:
virtual void funcB() { /* ... */ }
virtual ~B() {} // 确保B是多态的
};
class C : public A, public B {
public:
void funcA() override { /* 重写A::funcA */ }
virtual void funcC() { /* 新的虚函数 */ }
virtual ~C() {} // 确保C是多态的
};
基于这个定义,下面是一个简化后的结构图,展示类A、B和C的虚函数表(vtable)以及对象实例中的虚函数指针(vptr)。
虚函数表结构图
+-----------------+ +-----------------+
| vtable for A | | vtable for B |
|-----------------| |-----------------|
| &A::funcA | | &B::funcB |
| &A::~A | | &B::~B |
+-----------------+ +-----------------+
^ ^
| |
| |
+-----------------+ +-----------------+
| vptr (for A) | | vptr (for B) |
|-----------------| |-----------------|
| vtable for C | | vtable for C |
| (from A) | | (from B) |
+-----------------+ +-----------------+
\ /
\ /
\ /
\ /
+-------------+
| vtable for C |
|-----------------|
| &C::funcA | // 重写了A::funcA
| &B::funcB | // 继承自B
| &C::funcC | // 新的虚函数
| &C::~C | // 析构函数
+-----------------+
对象实例结构图
对于类C的一个实例c,其内存布局可能如下所示:
+-----------------+ +-----------------+
| vptr (for A) | | vptr (for B) |
|-----------------| |-----------------|
| vtable for C | | vtable for C |
| (from A) | | (from B) |
+-----------------+ +-----------------+
\ /
\ /
\ /
\ /
+-------------+
| Data for C | // 类C的数据成员
+-------------+
在这个例子中:
c有两个vptr,一个指向从A继承来的部分,另一个指向从B继承来的部分。- 每个vptr都指向C自己的虚函数表,但是虚函数表的内容根据基类的不同而不同。
- 如果通过A的引用或指针调用
funcA(),会使用第一个vptr找到对应的虚函数表,并执行C::funcA。 - 如果通过B的引用或指针调用
funcB(),会使用第二个vptr找到对应的虚函数表,并执行B::funcB。 funcC()是C特有的,因此只能通过C的引用或指针直接访问。

1万+

被折叠的 条评论
为什么被折叠?



