上一篇我们简单认识了C++的多态, 这次我们来看看多态的底层机制和原理. 不多哔哔, 直接开始.
文章目录
虚函数表(虚表)和虚表指针
1. 虚表和虚表指针的认识
首先我们来看一个常见的问题, 下面类的对象占几个字节
class Size {
public:
virtual void func1() {
cout << "func1()" << endl;
}
private:
int _a = 0;
};
按原来的知识点来说, 成员函数是不体现在对象中的, 所以一个Size对象是4字节
下面进行测试
void Test() {
cout << sizeof(Size) << endl;
}
我们可以看到结果是8, 显然不是上述的情况, Size类的对象模型如下
我们可以看到, 除了_a以外, 还有一个_vfptr
这个_vfptr我们叫做虚表指针 (v代表virtual,f代表function)
一个含有虚函数的类中都至少都有一个虚函数表指针,因为虚函数的地址要被放到虚函数表中,虚函数表也简称虚表
我们要明确一下概念
- 虚函数本身不占用对象空间
- 使用虚函数后, 对象中会有一个虚表指针
- 虚表: 存放虚函数的地址 (是一个存放虚函数指针的数组)
- 虚表指针: 指向虚表的首地址 (本身是一个二级指针)
- 普通的成员函数地址不会放在虚表中
2. 其他值得注意的问题
下面我们先看一段代码
class Base0 {
public:
virtual void func1() {
cout << "base::func1()" << endl;
}
virtual void func2() {
cout << "base::func2()" << endl;
}
//普通成员函数地址不会存放在虚表中
void func3() {
cout << "base::func2()" << endl;
}
private:
int _a = 1;
};
class Derive1 : public Base0 {
public:
//重写func1
virtual void func1() {
cout << "derive::func1()" << endl;
}
//子类新增的虚函数
virtual void func4() {
cout << "derive::func4()" << endl;
}
private:
int _d = 0;
};
void Test2() {
Base0 b;
Derive1 d;
}
下面给出对象b和d的内存模型
1. 我们可以看到, 父类有一张虚表, 子类也继承下来一张虚表, 但是两张虚表的地址是不同的(看vfptr的值即可)
2. 明显可以看到, 子类重写了父类的func1函数, 在虚表中存的是子类的func1函数, 我们把这这个叫做虚函数的重写, 也叫覆盖
3. 虚函数表本质是一个存虚函数指针的指针数组,这个数组以空指针nullptr结尾
总结一下派生类的虚表生成 :
- a.先将基类中的虚表内容拷贝一份到派生类虚表中
- b.如果派生类重写了基类中某个虚函数,用派生类自己的虚函数覆盖虚表中基类的虚函数
- c.派生类自己新增加的虚函数按其在派生类中的声明次序增加到派生类虚表的最后。(vs的调试器不可见, 后面我们会手动调用虚函数以验证)
3. 验证虚表存在哪里
下面我们通过一段代码验证在VS中虚表大致存在哪里, 只是大概的验证, 并不能精确的找到
void tes