探究C++:虚函数表究竟怎么回事?

封装,继承和多态,是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的结果:

扫描二维码关注公众号,回复: 13121785 查看本文章

比之前增加了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有两张虚表! 而且在内存中存储顺序如下图:

需要注意的是,上述所有类的析构函数也应该定义为虚析构函数,本文只是为了说明虚函数表,所以并未展示出虚析构函数的代码。 

猜你喜欢

转载自blog.csdn.net/sinat_21107433/article/details/108552472
今日推荐