多态和虚函数
在C++中,实现多态性的一个常见方法是使用虚函数。当一个类包含虚函数时,编译器会为这个类创建一个虚函数表(vtable),该表列出了该类的虚函数地址。每个包含虚函数的对象都有一个指针,指向其对应的虚函数表,这个指针成为虚指针(vptr)。
内存布局
为了理解子类在内存中的分布,我们可以看一个包含基类和子类的简单示例,解释它们的内存布局。
示例代码
#include <iostream>
class Base {
public:
virtual void func() { std::cout << "Base::func()" << std::endl; }
int baseData;
};
class Derived : public Base {
public:
void func() override { std::cout << "Derived::func()" << std::endl; }
int derivedData;
};
int main() {
Derived d;
d.func();
return 0;
}
内存布局
运行时,对于类的实例对象,内存分布一般如下:
- 基类的成员变量
- 虚指针(vptr)
- 子类的成员变量
内存分布图示
假设在32位系统中,一个指针大小是4字节,int
类型是4字节:
内存布局:
-
对于类
Base
的对象:+---------+--------------+-----------+
| vptr | baseData | (padding) |
+---------+--------------+-----------+
| 4 bytes | 4 bytes | (可能有) |
+---------+--------------+-----------+ -
对于类
Derived
的对象:+---------+--------------+--------------+
| vptr | baseData | derivedData |
+---------+--------------+--------------+
| 4 bytes | 4 bytes | 4 bytes |
+---------+--------------+--------------+
解释:
- vptr: 虚函数表指针(vptr)。指向表中包含所有虚函数地址的虚函数表。
- baseData: 基类的成员变量存储位置。
- derivedData: 子类的成员变量存储位置。
当编译器创建一个包含虚函数的类时,它会将该类的所有虚函数放在一个虚函数表(vtable)中,并在类对象中添加一个vptr以指向该vtable。这样,编译器可以动态地(在运行时)根据vptr指向的vtable中的函数地址来调用适当的函数实现。
动态调用过程
当d.func()
被调用时,编译器实际进行如下操作:
- 通过对象
d
的vptr找到虚函数表。 - 根据函数的偏移量找到虚函数表中的函数地址。
- 调用函数,此时实际调用的是
Derived::func()
。
总结
子类对象在内存中的分布反映了以下几点:
- 继承:子类首先包含基类的成员,这些成员在内存中具有与基类对象一致的布局。
- 多态性:对于包含虚函数的类对象,内存中额外包含了一个指向虚函数表的指针(vptr)。
- 扩展:子类对象在基类成员之后继续定义自身的成员。
通过理解这些内存布局及其工作原理,可以更深入地理解在C++ 中如何实现多态性及其内存开销和性能影响。