我在这就不详细说多态、虚函数是什么了,简单理解为:
1.基类定义虚函数
2.派生类重定义/重写(override)基类的虚函数
3.基类指针(引用)指向(绑定)到派生类对象
4.基类指针(引用)调用虚函数
就可以实现 基类指针(引用)有多种形态
想通过一个代码案例去进行分析,代码来自C++虚函数表剖析 | Leo的技术分享,下面的图片也是参考的这个文章:
#include <iostream>
using namespace std;
class A {
public:
virtual void vfunc1() { cout << "A::vfunc1" << endl; }
virtual void vfunc2() { cout << "A::vfunc2" << endl; }
void func1() { cout << "A::func1" << endl; }
void func2() { cout << "A::func2" << endl; }
private:
int m_data1, m_data2;
};
class B : public A {
public:
virtual void vfunc1() { cout << "B::vfunc1" << endl; }
void func1() { cout << "B::func1" << endl; }
private:
int m_data3;
};
class C: public B {
public:
virtual void vfunc2() { cout << "C::vfunc2" << endl; }
void func2() { cout << "C::func2" << endl; }
private:
int m_data1, m_data4;
};
int main() {
A a;
B b;
C c;
A* pa = &a;
A* pb = &b;
A* pc = &c;
B* pb2 = &c;
pa->vfunc1();
pa->vfunc2();
pb->vfunc1();
pb->vfunc2();
pc->vfunc1();
pc->vfunc2();
pb2->vfunc1();
pb2->vfunc2();
return 0;
}
输出为:
A::vfunc1
A::vfunc2
B::vfunc1
A::vfunc2
B::vfunc1
C::vfunc2
B::vfunc1
C::vfunc2
首先,只要类中有虚函数(不管是继承的、还是重写的、或者是新建的),都会有虚函数表,而虚函数表存在于类的内存布局中,当类实例化对象的时候,会在对象的内存布局前加上一个虚函数表指针,用来指向虚函数表。
对于上面中的类A,其虚函数表为:(注意,虚函数表是位于类的,而不是某个具体的对象)
虚函数表 A::vtable
-------------------
0: A::vfunc1
1: A::vfunc2
注意,对于非虚函数,其实就是代码段,是类所有的对象共享的,而数据成员是属于某个特定对象的。
所以对于对象a,其内存布局为:
// 基类 A 的内存模型
|-----------------|
| A::vtable ptr | (指向 A::vtable)
|-----------------|
| m_data1 |
| m_data2 |
|-----------------|
其中A::vtable ptr为虚函数表指针,指向虚函数表。
接着看类B,虚函数表为:
虚函数表 B::vtable (继承自 A::vtable)
-------------------
0: B::vfunc1
1: A::vfunc2
其中B::vfunc1是类B重写(override)的虚函数,而A::vfunc2是类B继承自类A的,未进行重写。
对于对象b,其内布局为:
// 派生类 B 的内存模型
|-----------------|
| B::vtable ptr | (指向 B::vtable)
|-----------------|
| m_data1 (A) |
| m_data2 (A) |
| m_data3 (B) |
|-----------------|
接着看类C,虚函数表为:
虚函数表 C::vtable (继承自 B::vtable)
-------------------
0: B::vfunc1
1: C::vfunc2
其中C::vfunc2是类重写(override)的虚函数(离C最近的继承->类B),而B::vfunc1是类C继承自类B的,未进行重写。
这里需要注意,不是要把重写的虚函数放到继承的虚函数前,虚函数表内的虚函数指针布局是根据函数申明顺序来的!
对于对象c,其内布局为:
|-----------------|
| C::vtable ptr | (指向 C::vtable)
|-----------------|
| m_data1(A) |
| m_data2(A) |
| m_data3(B) |
| m_data1(C) |
| m_data4(C) |
|-----------------|
注意C中有2个m_data1,一个继承来自类A,一个为C自己定义的。
总的来说:“对象的虚表指针用来指向自己所属类的虚表,虚表中的函数指针会指向其继承的最近的一个类的虚函数”
参考:
C++虚函数表剖析 | Leo的技术分享
Chapter12:多态——从虚函数表到RTTI(二) - 知乎
虚函数表存储的位置(解析C++内存分配及其编译分段)_c++虚表存在哪里-CSDN博客