C++中虚析构函数的作用及原理

Posted joker D888

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++中虚析构函数的作用及原理相关的知识,希望对你有一定的参考价值。

C++中虚析构函数的作用及原理

先测测你哟,上代码🧐:

#include<iostream>
using namespace std;
class Base	//父类(基类)
{
public:
	Base()
	{
		cout << "Base构造函数!" << endl;
	}
	~Base()
	{
		cout << "Base析构函数!" << endl;
	}
};

class Son : public Base		//子类(派生类)
{
public:
	Son()
	{
		cout << "Son构造函数!" << endl;
	}
	~Son()
	{
		cout << "Son析构函数!" << endl;
	}

};
问一:结合上面代码说出下面代码输出内容
int main()
{
    Son s;
	return 0;
}

相信大家都能轻松做出来吧。

在这里插入图片描述

答:继承中 先调用父类构造函数,再调用子类构造函数,析构顺序与构造相反

问二:那么将main()函数内容更换如下,再答出其输出内容
int main() 
{
	Base* s = new Son;
	delete s;	//释放
	s = NULL;
	return 0;
}

这个怎么样,跟你想的一样吗😉

在这里插入图片描述

哎🤔,你会发现,怎么没有调用Son的析构函数呢?

答:对于Base* s = new Son,在堆区开辟了一份空间,在使用delete s 时,因为s是一个Base类型的指针,所以delete时会调用Base的析构函数,而不会调用Son的析构函数。直到程序结束前Son都一直在堆区中,没有机会执行析构函数,所以程序不会输出“Son析构函数”,只有程序结束了堆区的空间才会由系统释放。

那么问题来了,在多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码

(子类在堆区开辟的内存往往由子类的析构函数统一释放)

例如下面的情况,子类中有在堆区开辟的内存

#include<iostream>
using namespace std;
class Base	//父类(基类)
{
public:
	Base()
	{
		cout << "Base构造函数!" << endl;
	}
	~Base()
	{
		cout << "Base析构函数!" << endl;
	}
};

class Son : public Base		//子类(派生类)
{
public:
	Son()
	{
        m_Name=new string("张三,李四,王五");
		cout << "Son构造函数!" << endl;
	}
	~Son()
	{
        if (this->m_Name != NULL)	//释放
        {
			delete m_Name;
			m_Name = NULL;
		}
		cout << "Son析构函数!" << endl;
	}
public:
    string* m_Name;		//用来接收堆区开辟的内容
};
int main() 
{
	Base* s = new Son;
	delete s;	//释放
	s = NULL;
	return 0;
}

就这个程序中,子类中有属性开辟到堆区,而程序的输出结果依然没有“Son析构函数”,说明析构函数中释放内存的动作根本不会被执行,而这就会导致会导致内存泄露,这就是父类指针在释放时无法调用到子类的析构代码所产生的问题。

现在终于来到正题了,虚析构函数有啥用?

答:虚析构和纯虚析构都可以解决上述问题,只要将父类中的析构函数改为虚析构或者 纯虚析构,Base类中有虚析构函数,delete Base就会先执行子类的析构函数再执行父类的析构函数。

#include<iostream>
using namespace std;
class Base	//父类(基类)
{
public:
	Base()
	{
		cout << "Base构造函数!" << endl;
	}
	virtual ~Base()		//加上virtual即可变为虚析构
	{
		cout << "Base析构函数!" << endl;
	}
	//或者用纯虚析构也可以,不过纯虚析构要进行函数实现
	//virtual ~Base()=0;
};
//纯虚析构函数的实现
//Base::~Base()
//{
//	cout << "Base析构函数!" << endl;
//}
class Son : public Base		//子类(派生类)
{
public:
	Son()
	{
        m_Name=new string("张三,李四,王五");
		cout << "Son构造函数!" << endl;
	}
	~Son()
	{
        if (this->m_Name != NULL)	//释放
        {
			delete m_Name;
			m_Name = NULL;
		}
		cout << "Son析构函数!" << endl;
	}
public:
    string* m_Name;		//用来接收堆区开辟的内容
};
int main() 
{
	Base* s = new Son;
	delete s;	//释放
	s = NULL;
	return 0;
}

程序结果如图: 在这里插入图片描述

那么这种做法的原理是什么,虚析构函数为什么会自动再调用父类的析构函数?

答:1.根据指针或者引用所指对象的实际类型然后调用相应的析构函数,同时对于所有派生类的析构函数,编译器都会插入调用直接基类的析构函数的代码。

  1. 多态的作用,对于普通对象 Son s,由于继承的原因,Son类中包含父类Base,在析构时编译器在调用Son类析构函数后会自动调用Base的析构函数(问一)。但在使用指针的情况下对于Base* s = new Son,在堆区开辟了一份空间,在使用delete s 时,因为s是一个Base类型的指针,所以delete时会调用Base的析构函数,而不会调用Son的析构函数(问二)。如果析构函数为虚函数,采用后期绑定,在delete s时,编译器会根据s所指的实际类型来调用析构函数:s实际指向Son类型,调用Son类的析构函数,相当于Son继承Base,调用Son的析构函数后自动调用Base的析构函数。

    (参考原帖:http://bbs.csdn.net/topics/380022416)

总的来说:子类的虚析构函数调用后会自动调用父类的析构函数,这个是编译器强制规定的

欢迎评论提出你的问题或看法

以上是关于C++中虚析构函数的作用及原理的主要内容,如果未能解决你的问题,请参考以下文章

C++中虚析构函数的作用

C++中虚析构函数的作用

C++ 不使用虚析构的后果及分析

C++虚析构函数

C++中基类的析构函数为什么要用virtual虚析构函数

C++ 类的多态四(虚析构函数的重要性)