7. 虚函数表

虚函数表 (又称虚表)是 C++ 编译器为实现运行时多态而建立的一张查找表,此查找表记录是记录该类对象的所有虚成员函数地址的表。

虚函数表是一个函数指针的数组,每个指针都指向他的虚成员函数。

虚函数表由 C++ 编译器在编译期间确定,并在运行时保存于内存中,当创建含有虚函数的对象时,对象内存的起始地址会存放一个(或多个)指针指向这个虚函数表。

虚函数表是 C++ 实现运行时多态的基础。

说明

示例1

没有虚函数表的类创建的对象的内存结构分析

// filename: vtable1.cpp
#include <iostream>

using namespace std;

// 点类(描述一个点的位置,面积等信息)
class Point {
    public:
        Point(float ax=0, float ay=0):x(ax), y(ay) { }
        // 定义为普通成员函数
        void info(void) {
            cout << "点(" << x << "," << y << ")\n";
        }
        void moveTo(float new_x, float new_y) {
            x = new_x; y = new_y;
        }
        // 定义为普通成员函数
        float getArea(void) {
            return 0;
        }
    public:
        float x;
        float y;
};
// 圆类(描述圆的位置、半径、面积等信息)
class Circle : public Point {
    public:
        Circle(float ax, float ay, float radius)
            : Point(ax, ay), r(radius) { }
        void info(void) {  // 子类的此函数也为虚函数
            cout << "圆(" << x << "," << y << "," << r << ")\n";
        }
        float getArea(void) {
            return 3.14*r*r;
        }
    public:
        float r; // 半径
};

int main(int argc, char * argv[]) {
    Point p1(1, 2);
    Circle c1(3, 4, 5);
    Point * p = &c1;

    cout << "sizeof(p1):" << sizeof(p1) << endl;
    cout << "sizeof(c1):" << sizeof(c1) << endl;
    p->info();

    return 0;
}

编译和运行结果如下:

weimz@mzstudio:~$ g++ -o vtable1 vtable1.cpp
weimz@mzstudio:~$ ./vtable1 
sizeof(p1):8
sizeof(c1):12
点(3,4)

指针 p 的类型是 Point*,它执行 Circle 类型的对象 c1,因为 Point::info() 不是虚函数,因此 p->info() 会根据 p 的类型来确定执行 Point::info() 函数。

内存结构分析

此时 p1c1 两个对象的内存结构如下:

点 p1 的内存结构
+--------+
|  x: 1  |
|  y: 2  |
+--------+

圆 c1 的内存结构
+--------+
|  x: 3  |
|  y: 4  |
|  r: 5  |
+--------+

示例2

void Point::info() 成员函数声明为虚函数,查看运行结果并分析内存结构,代码修改如下:

// filename: vtable2.cpp
#include <iostream>

using namespace std;

// 点类(描述一个点的位置,面积等信息)
class Point {
    public:
        Point(float ax=0, float ay=0):x(ax), y(ay) { }
        // 定义为虚函数
        virtual void info(void) {
            cout << "点(" << x << "," << y << ")\n";
        }
        void moveTo(float new_x, float new_y) {
            x = new_x; y = new_y;
        }
        float getArea(void) {
            return 0;
        }
    public:
        float x;
        float y;
};
// 圆类(描述圆的位置、半径、面积等信息)
class Circle : public Point {
    public:
        Circle(float ax, float ay, float radius)
            : Point(ax, ay), r(radius) { }
        void info(void) {  // 子类的此函数也为虚函数
            cout << "圆(" << x << "," << y << "," << r << ")\n";
        }
        float getArea(void) {
            return 3.14*r*r;
        }
    public:
        float r; // 半径
};

int main(int argc, char * argv[]) {
    Point p1(1, 2);
    Circle c1(3, 4, 5);
    Point * p = &c1;

    cout << "sizeof(p1):" << sizeof(p1) << endl;
    cout << "sizeof(c1):" << sizeof(c1) << endl;
    p->info();
    cout << "p->getArea() 返回结果:" << p->getArea() << endl;

    return 0;
}

编译和运行结果如下:

weimz@mzstudio:~$ g++ -o vtable2 vtable2.cpp
weimz@mzstudio:~$ ./vtable2 
sizeof(p1):16
sizeof(c1):24
p->getArea() 返回结果:0
圆(3,4,5)

从运行结果可以看出, p1p2 两个对象的内存结构变大。p->getArea() 依旧调用 Point::getAear() 成员函数,而 p->info() 则调用 Circle::info() 这个虚函数。

内存结构分析

此时 p1c1 两个对象的内存结构如下:

点 p1 的内存结构    虚表
+--------+      +--------+
| __vptr | ---> |   [0]  | --->  "void Point::info()"
|  x: 1  |      +--------+
|  y: 2  |
|  r: 5  |
+--------+

圆 c1 的内存结构    虚表
+--------+      +--------+
| __vptr | ---> |   [0]  | --->  "void Circle::info()"
|  x: 3  |      +--------+
|  y: 4  |
|  r: 5  |
+--------+

示例3

void Point::info()float Point::getArea(void) 两个成员函数都声明为虚函数,查看运行结果并分析内存结构。

// filename: vtable3.cpp
#include <iostream>

using namespace std;

// 点类(描述一个点的位置,面积等信息)
class Point {
    public:
        Point(float ax=0, float ay=0):x(ax), y(ay) { }
        // 定义为虚函数
        virtual void info(void) {
            cout << "点(" << x << "," << y << ")\n";
        }
        void moveTo(float new_x, float new_y) {
            x = new_x; y = new_y;
        }
        // 定义为虚函数
        virtual float getArea(void) {
            return 0;
        }
    public:
        float x;
        float y;
};
// 圆类(描述圆的位置、半径、面积等信息)
class Circle : public Point {
    public:
        Circle(float ax, float ay, float radius)
            : Point(ax, ay), r(radius) { }
        void info(void) {  // 子类的此函数也为虚函数
            cout << "圆(" << x << "," << y << "," << r << ")\n";
        }
        float getArea(void) {
            return 3.14*r*r;
        }
    public:
        float r; // 半径
};

int main(int argc, char * argv[]) {
    Point p1(1, 2);
    Circle c1(3, 4, 5);
    Point * p = &c1;

    cout << "sizeof(p1):" << sizeof(p1) << endl;
    cout << "sizeof(c1):" << sizeof(c1) << endl;
    p->info();
    cout << "p->getArea() 返回结果:" << p->getArea() << endl;

    return 0;
}

编译和运行结果如下:

weimz@mzstudio:~$ g++ -o vtable3 vtable3.cpp
weimz@mzstudio:~$ ./vtable3 
sizeof(p1):16
sizeof(c1):24
圆(3,4,5)
p->getArea() 返回结果:78.5

从运行结果可以看出, p->getArea() 调用 Circle::getAear() 成员函数,且 p->info() 也调用 Circle::info() 这个虚函数。

内存结构分析

此时 p1c1 两个对象的内存结构如下:

 p1 的内存结构    虚表
+--------+      +--------+
| __vptr | ---> |   [0]  | --->  "void Point::info()"
|  x: 1  |      |   [1]  | --->  "float Point::getArea()"
|  y: 2  |      +--------+
|  r: 5  |
+--------+

 c1 的内存结构    虚表
+--------+      +--------+
| __vptr | ---> |   [0]  | --->  "void Circle::info()"
|  x: 3  |      |   [1]  | --->  "float Circle::getArea()"
|  y: 4  |      +--------+
|  r: 5  |
+--------+