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.根据指针或者引用所指对象的实际类型然后调用相应的析构函数,同时对于所有派生类的析构函数,编译器都会插入调用直接基类的析构函数的代码。
-
多态的作用,对于普通对象 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++中虚析构函数的作用及原理的主要内容,如果未能解决你的问题,请参考以下文章