了解C++类的内存布局以及成员函数是怎么调用,有助于理解C++多态的实现原理。
无多态对象布局
单个类:
定义一个类A
class A
{
public:
A(int a1 = 0, int a2 = 0);
~A();
void A1();
protected:
int a1;
int a2;
};
如果类没有虚函数,那么class的布局和struct布局一样,只有成员变量,对象内存布局如下图:
继承类:
定义一个类B继承上面的类A
class B : public A
{
public:
B(int a1 = 0, int a2 = 0, int b1 = 0);
void B1();
protected:
int b1;
};
子类的构造函数是先构造父类,再构造子类,所以B对象内存布局只要在A成员之后加上B的成员即可。
多态下对象的内存布局
单个类:
定义一个类,带有虚函数
class A
{
public:
A(int a1 = 0, int a2 = 0);
~A();
virtual void A1();
virtual void A2();
virtual void A3();
protected:
int a1;
int a2;
};
因为class A带有虚函数,所以A对象的内存布局要增加一个虚函数表指针,它是一个指针,指向类A的虚函数表。
单继承:
定义如下
class B : public A
{
public:
B(int a1 = 0, int a2 = 0, int b1 = 0);
virtual void B1();
virtual void B2();
virtual void A1();
protected:
int b1;
};
当父类有虚函数时,子类继承父类的虚函数表,而且虚函数的顺序是:先父类的虚函数,再子类的虚函数。当父类的虚函数被子类重写时,则虚函数表中的父类虚函数指针要替换为子类的虚函数。
使用VS2017查看对象内存布局如下:
class A size(12):
+---
0 | {vfptr}
4 | a1
8 | a2
+---
A::$vftable@:
| &A_meta
| 0
0 | &A::A1
1 | &A::A2
2 | &A::A3
class B size(16):
+---
0 | +--- (base class A)
0 | | {vfptr}
4 | | a1
8 | | a2
| +---
12 | b1
+---
B::$vftable@:
| &B_meta
| 0
0 | &B::A1
1 | &A::A2
2 | &A::A3
3 | &B::B1
4 | &B::B2
验证:
为了验证之前的对象模型是否正确,查看一下下面的程序。
#include <iostream>
using namespace std;
class A
{
public:
A(int a1 = 0, int a2 = 0) {};
~A() {};
virtual void A1() { cout << "A::A1() " << endl; };
virtual void A2() { cout << "A::A2() " << endl; };
virtual void A3() { cout << "A::A3() " << endl; };
protected:
int a1;
int a2;
};
class B : public A
{
public:
B(int a1 = 0, int a2 = 0, int b1 = 0) {};
~B() {};
virtual void B1() { cout << "B::B1() " << endl; };
virtual void B2() { cout << "B::B2() " << endl; };
virtual void A1() { cout << "B::A1() " << endl; };
protected:
int b1;
};
typedef void(*pfun)(); //函数指针
int main()
{
B *bp = new B;
pfun fun = NULL;
for (size_t i = 0; i < 5; i++)
{
fun = (pfun)*((long *)*(long*)bp + i);
fun();
}
cout << *((long *)*(long*)bp + 5) << endl;
system("PAUSE");
}
说明一下:
(long*)bp,将对象的指针类型转换为(long*)类型,用于取出虚函数表的地址.
*(long*)bp,*为取出指针指向的值.此式子即虚函数表的地址,也就是第一个虚函数的地址.
(long*)*(long*)bp,将虚函数表的地址指针转换为(long*),用于后续迭代.
*(long*)*(long*)bp,*求指针值,即为第一个虚函数的地址,最后转换为pfun指针.
运行结果:
B::A1()
A::A2()
A::A3()
B::B1()
B::B2()
0
请按任意键继续. . .
运行结果与上述一致。