详解C++虚函数的工作原理

2020-06-22 13:09:43丽君

我们可以看到在Base类的内存布局上,第一个位置上存放虚函数表指针,接下来才是Base的成员变量。另外,存在着虚函数表,该表里存放着Base类的所有virtual函数。

既然虚函数表指针通常放在对象实例的最前面的位置,那么我们应该可以通过代码来访问虚函数表,通过下面这段代码加深对虚函数表的理解:

#include "stdafx.h"
#include<iostream>
using namespace std;

class Base
{
public:
	virtual void fun1(){
		cout<<"base fun1!n";
	}
	virtual void fun2(){
		cout<<"base fun2!n";
	}
	virtual void fun3(){
		cout<<"base fun3!n";
	}

	int a;
};

int _tmain(int argc, _TCHAR* argv[])
{
	typedef void(*pFunc)(void);
	Base b;
	cout<<"虚函数表指针地址:"<<(int*)(&b)<<endl;

	//对象最前面是指向虚函数表的指针,虚函数表中存放的是虚函数的地址
	pFunc pfun;
	pfun=(pFunc)*((int*)(*(int*)(&b))); //这里存放的都是地址,所以才一层又一层的指针
	pfun();
	pfun=(pFunc)*((int*)(*(int*)(&b))+1);
	pfun();
	pfun=(pFunc)*((int*)(*(int*)(&b))+2);
	pfun();

	system("pause");
	return 0;
}

运行结果:

通过这个例子,对虚函数表指针,虚函数表这些有了足够的理解。下面再深入一些。C++又是如何利用基类指针和虚函数来实现多态的呢?这里,我们就需要弄明白在继承环境下虚函数表是如何工作的。目前只理解单继承,至于虚继承,多重继承待以后再理解。

单继承代码如下:

class Base
{
public:
	virtual void fun1(){
		cout<<"base fun1!n";
	}
	virtual void fun2(){
		cout<<"base fun2!n";
	}
	virtual void fun3(){
		cout<<"base fun3!n";
	}

	int a;
};

class Child:public Base
{
public:
	void fun1(){
		cout<<"Child fun1n";
	}
	void fun2(){
		cout<<"Child fun2n";
	}
	virtual void fun4(){
		cout<<"Child fun4n";
	}
};

内存布局对比:

通过对比,我们可以看到:

在单继承中,Child类覆盖了Base类中的同名虚函数,在虚函数表中体现为对应位置被Child类中的新函数替换,而没有被覆盖的函数则没有发生变化。 对于子类自己的虚函数,直接添加到虚函数表后面。

另外,我们注意到,类Child和类Base中都只有一个vfptr指针,前面我们说过,该指针指向虚函数表,我们分别输出类Child和类Base的vfptr:

int _tmain(int argc, _TCHAR* argv[])
{
	typedef void(*pFunc)(void);
	Base b;
	Child c;
	cout<<"Base类的虚函数表指针地址:"<<(int*)(&b)<<endl;
	cout<<"Child类的虚函数表指针地址:"<<(int*)(&c)<<endl;

	system("pause");
	return 0;
}