虚函数

95 阅读2分钟

1 虚函数表与 vptr 指针

如果类中有虚函数,那么在使用该类定义对象的时候,会创建一个虚函数表,虚函数表存放了虚函数的入口地址,而 vptr 指向这个虚函数表,父类的vptr指针指向父类的虚函数表,子类的vptr指针指向子类的虚函数表(虚函数的入口地址都在这个虚函数表中)。动态联编就是这么实现的,根据vptr指针决定调用哪个虚函数,当遇到虚函数调用时,会根据vptr指针找到虚函数表,并在虚函数表中查找函数原型。

vptr指针初始化是分步实现的

1.使用子类定义一个子类对象,会初始化子类的vptr指针 2.当父类的构造函数执行时,子类的 vptr 指针会指向父类的虚函数表 3.当父类的构造函数执行完毕时,子类的 vptr 指针指向子类的虚函数表

通过一个例子可以证明vptr指针的分步初始化

#include <iostream>
using namespace std;

class Class1
{
public:
	Class1()
	{
		print_func();
		cout << "class 1 构造" << endl;
	}
	virtual void print_func()
	{
		cout << "class 1" << endl;
	}
};

class Class2 : public Class1
{
public:
	Class2()
	{
		print_func();
		cout << "class 2 构造" << endl;
	}
	virtual void print_func()
	{
		cout << "class 2" << endl;
	}
};

int main()
{
	Class2 c2;
	
	system("pause");
	return 0;
}

编译运行可以看到,在Class1构造函数中调用了Class1的print_func函数,这说明此时vptr指针指向了Class1的虚函数表;在Class2构造函数中调用了Class2的print_func函数,说明此时vptr指针指向了Class2的虚函数表。

2 虚析构函数

虚析构函数是指,在父类析构函数加 virtual 关键字,通过父类指针,执行所有子类的析构函数,释放所有子类内存。

应用场景:一个函数的参数是基类指针,在主调函数 new 分配了内存,需要在被调函数中使用完该对象后 delete 释放内存,我在调用这个函数时传入了一个派生类对象,如果基类中析构函数没有加 virtual 关键字,就会静态联编,delete 时只执行基类的析构函数,而没有执行派生类析构函数,造成了派生类对象中内存泄漏。虚析构函数会执行动态联编,把基类和派生类的析构函数都执行一遍。

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;

class MyClassA
{
public:
	MyClassA(const char* str)
	{
		this->pA = new char[strlen(str) + 1];
		strcpy(this->pA, str);
		cout << "A 构造函数" << endl;
	} 
	//~MyClassA()
	virtual ~MyClassA()//虚析构函数
	{
		if (this->pA != NULL)
		{
			delete[] this->pA;
		}
		this->pA = NULL;
		cout << "A 析构函数 " << endl;
	}
private:
	char* pA;
};

class MyClassB : public MyClassA
{
public:
	MyClassB(const char* str) : MyClassA(str)
	{
		this->pB = new char[strlen(str) + 1];
		strcpy(this->pB, str);
		cout << "B 构造函数" << endl;
	}
	~MyClassB()
	{
		if (this->pB != NULL)
		{
			delete[] this->pB;
		}
		this->pB = NULL;
		cout << "B 析构函数 " << endl;
	}
private:
	char* pB;
};

class MyClassC : public MyClassB
{
public:
	MyClassC(const char* str) : MyClassB(str)
	{
		this->pC = new char[strlen(str) + 1];
		strcpy(this->pC, str);
		cout << "C 构造函数" << endl;
	}
	~MyClassC()
	{
		if (this->pC != NULL)
		{
			delete[] this->pC;
		}
		this->pC = NULL;
		cout << "C 析构函数 " << endl;
	}
private:
	char* pC;
};

void FuncTest(MyClassA* p)
{
	delete p;
}

int main()
{
	MyClassC* c = new MyClassC("hello C++");
	FuncTest(c);

	//delete c; //也会调用 C B A 的析构函数
	
	system("pause");
	return 0;
}

正常析构函数只会执行A(做函数参数的类)的析构函数,因为静态联编,编译器编译时并不知道具体要用哪个类的析构函数,所以只能选择函数参数的类的析构函数执行

将基类的析构函数加 virtual 关键字,即虚析构函数,将会依次调用 C B A(基类和所有派生类)的析构函数;动态联编,在执行时根据指针具体传入哪个类的对象决定如何调用