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随之变化

关键要点:

  1. 虚函数是实现运行时多态的基础

  2. 每个多态类都有自己的虚函数表

  3. 虚表指针在对象构造过程中自动设置

  4. 没有虚函数的类不会产生虚表指针开销

  5. 在构造函数中调用虚函数可能不会按预期工作(因为vptr可能指向基类的虚表)

这个机制确保了C++能够在运行时正确调用到实际对象的函数,实现了真正的多态行为。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐