7. 虚函数表
虚函数表 (又称虚表)是 C++ 编译器为实现运行时多态而建立的一张查找表,此查找表记录是记录该类对象的所有虚成员函数地址的表。
虚函数表是一个函数指针的数组,每个指针都指向他的虚成员函数。
虚函数表由 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() 函数。
内存结构分析
此时 p1 和 c1 两个对象的内存结构如下:
点 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)
从运行结果可以看出, p1 和 p2 两个对象的内存结构变大。p->getArea() 依旧调用 Point::getAear() 成员函数,而 p->info() 则调用 Circle::info() 这个虚函数。
内存结构分析
此时 p1 和 c1 两个对象的内存结构如下:
点 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() 这个虚函数。
内存结构分析
此时 p1 和 c1 两个对象的内存结构如下:
点 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 |
+--------+