C++虚函数机制详解
·
1. 虚函数的基本实现原理
虚函数表(vtable)和虚表指针(vptr)
cpp
class Animal {
public:
virtual void speak() { cout << "Animal sound" << endl; }
virtual void eat() { cout << "Animal eating" << endl; }
virtual ~Animal() {}
};
class Dog : public Animal {
public:
void speak() override { cout << "Woof!" << endl; }
void eat() override { cout << "Dog eating" << endl; }
};
内存布局示意图:
text
Dog对象内存布局: ┌─────────────┐ │ vptr │ → 指向Dog的虚函数表 ├─────────────┤ │ Animal部分 │ │ 成员变量 │ ├─────────────┤ │ Dog特有 │ │ 成员变量 │ └─────────────┘ Dog的虚函数表: ┌─────────────┐ │ &Dog::speak │ → 指向Dog::speak() ├─────────────┤ │ &Dog::eat │ → 指向Dog::eat() ├─────────────┤ │ &Dog::~Dog │ → 指向Dog的析构函数 └─────────────┘
2. 虚表存在的位置
存储位置分析
cpp
class Base {
public:
virtual void func1() {}
virtual void func2() {}
int data;
};
// 验证虚表位置
void checkVTableLocation() {
Base obj;
// 获取虚表指针
void** vptr = *(void***)&obj;
// 虚表本身存储在只读数据段(.rodata)
// 虚表指针存储在对象实例中(堆栈或堆上)
cout << "对象地址: " << &obj << endl;
cout << "虚表指针: " << vptr << endl;
cout << "第一个虚函数地址: " << vptr[0] << endl;
}
存储位置总结:
-
虚表(vtable):编译期生成,存储在程序的只读数据段
-
虚表指针(vptr):运行时存在于每个对象实例中
-
虚函数代码:存储在代码段
3. 没有虚函数时的虚表指针
关键结论:没有虚函数就没有虚表指针
cpp
class NoVirtual {
int x, y; // 只有普通成员变量
public:
void normalFunc() {} // 普通成员函数
};
class HasVirtual {
int x, y;
public:
virtual void virtualFunc() {} // 虚函数
};
void checkVptrExistence() {
NoVirtual nv;
HasVirtual hv;
cout << "NoVirtual大小: " << sizeof(nv) << endl; // 输出8(只有两个int)
cout << "HasVirtual大小: " << sizeof(hv) << endl; // 输出16(两个int + vptr)
// NoVirtual对象中没有虚表指针!
// HasVirtual对象中有虚表指针!
}
4. 虚表指针的大小
指针大小由系统架构决定
cpp
class TestVirtual {
virtual void func() {}
};
void checkVptrSize() {
TestVirtual obj;
// 在32位系统上:vptr大小为4字节
// 在64位系统上:vptr大小为8字节
cout << "对象大小: " << sizeof(obj) << endl;
cout << "指针大小: " << sizeof(void*) << endl;
cout << "vptr大小: " << sizeof(*(void**)&obj) << endl;
}
不同平台下的vptr大小:
-
32位系统:4字节
-
64位系统:8字节
-
其他架构:与指针大小相同
5. 虚表指针的绑定时机
构造和析构过程中的vptr绑定
cpp
class Base {
public:
Base() {
cout << "Base构造函数 - vptr指向: ";
printVTable();
}
virtual void func() { cout << "Base::func" << endl; }
virtual ~Base() {
cout << "Base析构函数 - vptr指向: ";
printVTable();
}
private:
void printVTable() {
void** vptr = *(void***)this;
cout << vptr << endl;
}
};
class Derived : public Base {
public:
Derived() {
cout << "Derived构造函数 - vptr指向: ";
printVTable();
}
void func() override { cout << "Derived::func" << endl; }
~Derived() override {
cout << "Derived析构函数 - vptr指向: ";
printVTable();
}
private:
void printVTable() {
void** vptr = *(void***)this;
cout << vptr << endl;
}
};
vptr绑定时机详细分析
cpp
class A {
public:
A() {
// 进入构造函数时,vptr已指向A的虚表
cout << "A构造 - 此时调用虚函数: ";
virtualFunc(); // 调用A::virtualFunc()
}
virtual void virtualFunc() { cout << "A::virtualFunc" << endl; }
};
class B : public A {
public:
B() {
// 进入构造函数时,vptr已指向B的虚表
cout << "B构造 - 此时调用虚函数: ";
virtualFunc(); // 调用B::virtualFunc()
}
void virtualFunc() override { cout << "B::virtualFunc" << endl; }
};
// 测试代码
void testConstruction() {
B b; // 输出:
// A构造 - 此时调用虚函数: A::virtualFunc
// B构造 - 此时调用虚函数: B::virtualFunc
}
6. 完整的虚函数机制示例
cpp
#include <iostream>
using namespace std;
// 基类
class Shape {
private:
double x, y;
public:
Shape(double x, double y) : x(x), y(y) {
cout << "Shape构造函数" << endl;
}
virtual double area() const {
cout << "Shape::area - ";
return 0;
}
virtual void draw() const {
cout << "Shape::draw at (" << x << ", " << y << ")" << endl;
}
virtual ~Shape() {
cout << "Shape析构函数" << endl;
}
};
// 派生类
class Circle : public Shape {
private:
double radius;
public:
Circle(double x, double y, double r) : Shape(x, y), radius(r) {
cout << "Circle构造函数" << endl;
}
double area() const override {
cout << "Circle::area - ";
return 3.14159 * radius * radius;
}
void draw() const override {
cout << "Circle::draw - 半径: " << radius << endl;
Shape::draw();
}
~Circle() override {
cout << "Circle析构函数" << endl;
}
};
// 测试函数
void demonstrateVirtual() {
cout << "=== 创建Circle对象 ===" << endl;
Circle circle(10, 20, 5);
cout << "\n=== 通过基类指针调用 ===" << endl;
Shape* shapePtr = &circle;
cout << "面积: " << shapePtr->area() << endl; // 动态绑定到Circle::area
shapePtr->draw(); // 动态绑定到Circle::draw
cout << "\n=== 对象析构 ===" << endl;
// 自动调用析构函数,注意vptr的变化
}
int main() {
demonstrateVirtual();
return 0;
}
7. 虚函数机制总结
| 特性 | 说明 |
|---|---|
| 虚表位置 | 编译期生成,存储在只读数据段 |
| 虚表指针 | 每个对象实例中都有一个,指向对应的虚表 |
| 无虚函数 | 没有虚表指针,对象大小较小 |
| vptr大小 | 与系统指针大小相同(32位4字节,64位8字节) |
| 绑定时机 | 在构造/析构函数开始时自动设置 |
| 构造顺序 | 基类→派生类,vptr随之变化 |
| 析构顺序 | 派生类→基类,vptr随之变化 |
关键要点:
-
虚函数是实现运行时多态的基础
-
每个多态类都有自己的虚函数表
-
虚表指针在对象构造过程中自动设置
-
没有虚函数的类不会产生虚表指针开销
-
在构造函数中调用虚函数可能不会按预期工作(因为vptr可能指向基类的虚表)
这个机制确保了C++能够在运行时正确调用到实际对象的函数,实现了真正的多态行为。
更多推荐
所有评论(0)