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

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

运行结果:

可以看到,类Child和类Base分别拥有自己的虚函数表指针vfptr和虚函数表vftable。

下面这段代码,说明了父类和基类拥有不同的虚函数表,同一个类拥有相同的虚函数表,同一个类的不同对象的地址(存放虚函数表指针的地址)不同。

int _tmain(int argc, _TCHAR* argv[])
{
	Base b;
	Child c1,c2;
	cout<<"Base类的虚函数表的地址:"<<(int*)(*(int*)(&b))<<endl;
	cout<<"Child类c1的虚函数表的地址:"<<(int*)(*(int*)(&c1))<<endl;	//虚函数表指针指向的地址值
	cout<<"Child类c2的虚函数表的地址:"<<(int*)(*(int*)(&c2))<<endl;

	system("pause");
	return 0;
}

在定义该派生类对象时,先调用其基类的构造函数,然后再初始化vfptr,最后再调用派生类的构造函数( 从二进制的视野来看,所谓基类子类是一个大结构体,其中this指针开头的四个字节存放虚函数表头指针。执行子类的构造函数的时候,首先调用基类构造函数,this指针作为参数,在基类构造函数中填入基类的vfptr,然后回到子类的构造函数,填入子类的vfptr,覆盖基类填入的vfptr。如此以来完成vfptr的初始化)。也就是说,vfptr指向vftable发生在构造函数期间完成的。

动态绑定例子:

#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;
};

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


int _tmain(int argc, _TCHAR* argv[])
{
	Base* p=new Child;
	p->fun1();
	p->fun2();
	p->fun3();

	system("pause");
	return 0;
}

运行结果:

结合上面的内存布局:

其实,在new Child时构造了一个子类的对象,子类对象按上面所讲,在构造函数期间完成虚函数表指针vfptr指向Child类的虚函数表,将这个对象的地址赋值给了Base类型的指针p,当调用p->fun1()时,发现是虚函数,调用虚函数指针查找虚函数表中对应虚函数的地址,这里就是&Child::fun1。调用p->fun2()情况相同。调用p->fun3()时,子类并没有重写父类虚函数,但依旧通过调用虚函数指针查找虚函数表,发现对应函数地址是&Base::fun3。所以上面的运行结果如上图所示。