封装,继承和多态,是C++的三大特性。提到多态,就会提到虚函数virtual;提到虚函数,不得不说虚函数表。我们知道,在一个类Class中,如果有定义虚函数,那么这个类对象所占用的存储空间中,会保存一个指向虚函数表的指针,结果是这个类的大小会增加4,即一个指针的大小。
那么这个指针存储在类的什么地方?虚函数表里是如何存放各个虚函数的?在具有继承关系的不同类中,虚函数表中的存储有什么变化?本文Jungle将对此做个测试。
1.有无虚函数,对类大小的影响
前文已经说到,如果一个类定义了虚函数,sizeof的结果会增加4,增加的是一个指向虚函数表的指针。这里简单做个测试验证一下。
定义一个类如下,我们用sizeof看下其大小是多少?
class Base {
public:
Base() {
cout << "Base::Base()" << endl;
a = 1;
b = 2;
}
void func_1() {
cout << "Base::func_1()" << endl;
}
~Base() {
cout << "Base::~Base()" << endl;
}
private:
int a;
int b;
};
控制台打印的结果如下:
占8个字节(2个int类型,4*2 = 8),内存中存储情况如下:
现在为类增加一些成员:
class Base {
public:
Base() {
cout << "Base::Base()" << endl;
a = 1;
b = 2;
}
void func_1() {
cout << "Base::func_1()" << endl;
}
virtual void func_2() {
cout << "Base::func_2()" << endl;
}
virtual void func_3() {
cout << "Base::func_3()" << endl;
}
~Base() {
cout << "Base::~Base()" << endl;
}
private:
int a;
int b;
};
如上代码,增加了两个虚函数func_2和func_3,再看下sizeof的结果:
比之前增加了4个字节。其实不论增加多少个虚函数,都只增加4个字节。
2.指向虚函数表的指针存储在哪里?
我们知道,增加的4个字节是类中存放了指向虚函数表的指针。那么这个指针存放在类的什么位置呢?——类的起始地址处。我们可以通过如下代码来测试下:
/*
* Function name: Test1
* Description : 测试类的虚函数表,类中包括两个虚函数
* 1. Print size of object base;
* 2. Print member of object base;
* 3. Get the address of virtual function and call it, which proves the existence of virtual function table.
* Return : None
*/
void Test1()
{
Base base;
Base* pBase = &base;
cout << "base Size: " << sizeof(base) << endl;
// 虚函数表地址放在对象开始处
printf("虚函数表地址: 0x%x\n", *(int*)pBase);
// 然后才存放其他成员变量
printf("%d\n", *(int*)((int*)pBase + 1));
printf("%d\n", *(int*)((int*)pBase + 2));
typedef void(*pFunc)();
pFunc fun;
for (int i = 0; i < 2; i++) {
fun = (pFunc)*((int*)(*(int*)pBase) + i);
fun();
}
}
代码运行结果如下:
3.虚函数表里存放的什么?
从上面的代码和运行结果可以得出结论,虚函数表里存放的是各个虚函数的地址,如下图:
4.继承关系下的虚函数表
4.1. 继承但不覆写
如果基类Base定义了虚函数,其子类继承基类的虚函数,并且子类也定义有自己的虚函数,这时候虚函数表会是什么情况呢?
代码如下所示,基类Base有普通成员函数func_1,两个虚函数func_2和func_3。派生类Base2除了继承Base的方法外,自己另外定义了两个成员变量c和d,以及两个虚函数func_4和func_5.
class Base {
public:
Base() {
cout << "Base::Base()" << endl;
a = 1;
b = 2;
}
void func_1() {
cout << "Base::func_1()" << endl;
}
virtual void func_2() {
cout << "Base::func_2()" << endl;
}
virtual void func_3() {
cout << "Base::func_3()" << endl;
}
~Base() {
cout << "Base::~Base()" << endl;
}
private:
int a;
int b;
};
class Base2 : public Base {
public:
Base2() {
c = 3;
d = 4;
}
virtual void func_4() {
cout << "Base2::func_4()" << endl;
};
virtual void func_5() {
cout << "Base2::func_5()" << endl;
};
private:
int c;
int d;
};
我们用如下的代码来测试下这种场景下虚函数表是如何安排的:
/*
* Function name: Test2
* Description : 测试继承类的虚函数表,基类中包括两个虚函数,派生类中包含两个虚函数,且不覆写
* 1. Print size of object base2;
* 2. Print member of object base2;
* 3. Get the address of virtual function and call it, which proves the existence of virtual function table.
* Return : None
*/
void Test2()
{
Base2 base2;
Base2* pBase2 = &base2;
cout << "base2 Size : " << sizeof(base2) << endl;
// 虚函数表地址放在对象开始处
printf("虚函数表地址:0x%x\n", *(int*)(pBase2));
// 然后存放其他成员变量
printf("%d\n", *(int*)((int*)pBase2 + 1));
printf("%d\n", *(int*)((int*)pBase2 + 2));
printf("%d\n", *(int*)((int*)pBase2 + 3));
printf("%d\n", *(int*)((int*)pBase2 + 4));
// 取出虚函数
typedef void(*pFunc)();
pFunc fun;
/*
* Base :: virtual func_2
* Base :: virtual func_3
* Base2:: virtual func_4
* Base2:: virtual func_5
*/
for (int i = 0; i < 4; i++) {
fun = (pFunc)*(((int*)*(int*)pBase2) + i);
fun();
}
}
代码运行结果如下图:
根据测试代码以及运行结果,我们可以得出结论,具有单继承关系的子类,其类对象中保存了一个指向虚函数表的指针,虚函数表依次存放基类的虚函数和自己定义的虚函数,接着保存基类的成员和自己定义的成员。如下图:
4.2. 继承,子类覆写父类虚函数
如果子类覆写了父类的虚函数呢?如下代码,子类Base3继承自Base,但Base3除了定义了自己的虚函数func_6外,重新实现了父类Base的虚函数func_3,这时候虚函数表情况是怎样的呢?
class Base3 :public Base {
public:
Base3() {
e = 5;
f = 6;
}
// 覆写func_3
virtual void func_3() {
cout << "Base3::func_3()" << endl;
};
virtual void func_6() {
cout << "Base3::func_6()" << endl;
};
private:
int e;
int f;
};
我们用如下代码打印一下:
/*
* Function name: Test3
* Description : 测试继承类的虚函数表,基类中包括两个虚函数,派生类中包含两个虚函数,覆写基类的其中一个虚函数
* 1. Print size of object base3;
* 2. Print member of object base3;
* 3. Get the address of virtual function and call it, which proves the existence of virtual function table.
* Return : None
*/
void Test3()
{
Base3 base3;
Base3* pBase3 = &base3;
cout << "base3 Size : " << sizeof(base3) << endl;
// 虚函数表地址放在对象开始处
printf("虚函数表地址:0x%x\n", *(int*)pBase3);
// 然后存放其他成员变量
printf("%d\n", *((int*)pBase3 + 1));
printf("%d\n", *((int*)pBase3 + 2));
printf("%d\n", *((int*)pBase3 + 3));
printf("%d\n", *((int*)pBase3 + 4));
typedef void(*pFunc)();
pFunc fun;
/*
* Base :: virtual func_2
* Base3:: virtual func_3
* Base3:: virtual func_6
*/
for (int i = 0; i < 3; i++) {
fun = (pFunc)*(((int*)*(int*)pBase3) + i);
fun();
}
}
代码运行结果如图:
可以看到,Base3的虚函数表中,依次存放的是Base的虚函数func_2,由于Base中的func_3被自己重新实现了,所以虚函数表中存放的是Base3定义的虚函数func_3,接下来是Base3的虚函数func_6.
4.3. 多重继承下的虚函数表
有两个父类Father1和Father2, Father1有成员int x, 虚函数func_1;Father2有成员int y,虚函数func_2。子类Child同时继承自Father1和Father2,此外还有成员int z和虚函数func_3。各个类的定义代码如下:
class Father1 {
public:
Father1() {
x = 111;
}
virtual void func_1() {
cout << "Father1::func_1()" << endl;
}
private:
int x;
};
class Father2 {
public:
Father2() {
y = 222;
}
virtual void func_2() {
cout << "Father2::func_2()" << endl;
}
private:
int y;
};
class Child :public Father1, public Father2 {
public:
Child() {
z = 333;
}
virtual void func_3() {
cout << "Child::func_3()" << endl;
}
private:
int z;
};
我们用如下代码进行打印:
/*
* Function name: Test4
* Description : 测试继承类的虚函数表,类Child有两个父类Father1和Father2
* 1. Print size of object Child;
* 2. Print member of object Child;
* 3. Get the address of virtual function and call it, which proves the existence of virtual function table.
* Return : None
*/
void Test4()
{
Child child;
Child* pChild = &child;
cout << "child Size : " << sizeof(child) << endl;
// 虚函数表地址放在对象开始处
printf("虚函数表 1 地址:0x%x\n", *(int*)pChild);
printf("虚函数表 2 地址:0x%x\n", *((int*)pChild + 2));
// 然后存放其他成员变量
printf("%d\n", *((int*)pChild + 1));
printf("%d\n", *((int*)pChild + 3));
printf("%d\n", *((int*)pChild + 4));
typedef void(*pFunc)();
pFunc fun;
/*
* Father1 :: virtual func_1
* Child:: virtual func_3
*/
for (int i = 0; i < 2; i++) {
fun = (pFunc)*(((int*)*(int*)pChild) + i);
fun();
}
/*
* Father2 :: virtual func_2
*/
for (int i = 0; i < 1; i++) {
fun = (pFunc)*(((int*)*((int*)pChild + 2)) + i);
fun();
}
}
运行结果如下:
我们可以看到,Child的size为20,说明除了3个整型占据12个字节外,还多出了8个字节,即Child有两张虚表! 而且在内存中存储顺序如下图:
需要注意的是,上述所有类的析构函数也应该定义为虚析构函数,本文只是为了说明虚函数表,所以并未展示出虚析构函数的代码。