C++ virtual(虚函数)
虚函数和虚函数表(vtable)是 C++ 中实现多态性的核心机制。它们允许在运行时根据对象的实际类型调用正确的虚函数实现,从而支持动态绑定和多态性。以下是对虚函数和虚函数表的详细解释:
1. 虚函数
- 虚函数 是在基类中声明的函数,并且在派生类中可以被重写(override)。它的声明通过
virtual
关键字标记。 - 目的:允许在运行时根据对象的实际类型调用正确的函数实现,从而支持多态性。
示例
#include <iostream>
using namespace std;
class Base {
public:
virtual void foo() { cout << "Base::foo" << endl; }
};
class Derived : public Base {
public:
void foo() override { cout << "Derived::foo" << endl; }
};
int main() {
Base* b = new Derived();
b->foo(); // Output: Derived::foo
delete b;
return 0;
}
在上面的示例中,尽管 b
的类型是 Base*
,它实际指向一个 Derived
对象。调用 b->foo()
时,程序会根据 Derived
类的实现来调用 foo
函数,这就是通过虚函数实现的多态性。
2. 虚函数表(vtable)
- 虚函数表 是一个由函数指针组成的表格,用于存储类的虚函数的地址。每个含有虚函数的类都有一个虚函数表。
- 虚函数表的作用:支持动态绑定,确保在运行时调用正确的虚函数实现。
虚函数表的组成
- 每个虚函数表条目:
- 存储指向虚函数的指针。当虚函数被调用时,通过这些指针来找到并调用正确的函数实现。
- 虚函数表指针(vptr):
- 对象中包含一个指针,称为
vptr
,它指向该对象所属类的虚函数表。每个对象在创建时会被初始化其vptr
。
- 对象中包含一个指针,称为
示例
class A {
public:
virtual void foo() { cout << "A::foo" << endl; }
virtual void bar() { cout << "A::bar" << endl; }
};
class B : public A {
public:
void foo() override { cout << "B::foo" << endl; }
};
int main() {
B b;
A* a = &b;
a->foo(); // Output: B::foo
a->bar(); // Output: A::bar
return 0;
}
在这个例子中:
A
类有两个虚函数foo
和bar
。B
类重写了foo
函数。B
类对象的虚函数表只包含B::foo
和A::bar
的指针。A* a = &b;
通过a
调用foo
和bar
时,会通过B
的虚函数表来找到B::foo
和A::bar
的实现。
3. 内存布局
- 对象内存布局:每个对象在内存中通常包含一个指向虚函数表的指针(
vptr
),vptr
指向虚函数表。虚函数表中包含指向虚函数实现的指针。 - 虚函数表内存布局:
- 虚函数表的每个条目通常是一个函数指针,指向虚函数的实际实现。
4. 虚函数表的工作原理
- 类的虚函数表生成:
- 编译器为每个包含虚函数的类生成一个虚函数表,并填充虚函数的地址。
- 对象创建:
- 当创建一个对象时,编译器将该对象的
vptr
初始化为指向类的虚函数表。
- 当创建一个对象时,编译器将该对象的
- 函数调用:
- 当通过基类指针或引用调用虚函数时,程序通过
vptr
查找虚函数表,并调用虚函数表中对应的函数实现。
- 当通过基类指针或引用调用虚函数时,程序通过
5. 多重继承和虚函数表
- 多重继承 时,每个基类有一个虚函数表。子类对象包含多个
vptr
,每个vptr
指向一个基类的虚函数表。 - 虚函数表的合并:
- 在多重继承中,编译器会处理虚函数表的合并,以确保每个基类的虚函数表能够正确地映射到子类对象的
vptr
上。
- 在多重继承中,编译器会处理虚函数表的合并,以确保每个基类的虚函数表能够正确地映射到子类对象的
总结
- 虚函数 是支持 C++ 多态性的关键,通过
virtual
关键字声明。 - 虚函数表 存储虚函数的地址,支持动态绑定和多态性。
- 虚函数表指针(vptr) 是对象的一部分,指向虚函数表,确保运行时调用正确的函数实现。
- 在 多重继承 中,子类对象会维护多个
vptr
,每个指向不同基类的虚函数表。
虚函数指针初始化时机,虚函数指针在构造函数开始时就会初始化。在构造函数中调用虚函数是没有意义的因为构造函数会从父类到子类层层递进构造,虚函数指针指向也会不断修改。在构造函数中调用虚函数并不能实现多态。