虚析构函数可以保证子类析构函数的调用,这样做有什么好处呢?其实这样可以防止内存泄漏。我们接下来看一下实例,假如有如下需求
定义一个动物类,动物会叫声明一个Say()函数,子类去各自实现这个Say()表示各种不同的动物的叫声。
我们定义两个动物的子类,狗Dog和猫Cat,为了方便查看析构函数是否调用,我们在子类和父类的构造函数和析构函数中都分别输出一段文字。
首先来看错误的写法:
class Animal {
public:
int id;
public:
void Say()
{
cout<< "动物叫~~~" <<endl;
}
Animal()
{
cout<< "---------------------动物构造函数-----申请内存" <<endl;
}
//如果直接delete父类 可能会导致内存泄漏
~Animal()
{
cout<< "---------------------动物析构函数-----释放内存" <<endl;
}
};
class Dog: public Animal{
public:
char * str;
Dog(){
cout<< "---------------------狗子构造函数-----申请内存" <<endl;
str = new char[1024*1024*8];
}
~Dog()
{
cout<< "---------------------狗子析构函数-----释放内存" <<endl;
delete str;
}
void Say()
{
cout<< "汪汪汪~~~" <<endl;
}
};
class Cat:public Animal{
public:
char * str;
Cat(){
cout<< "---------------------猫猫构造函数-----申请内存" <<endl;
str = new char[10000];
}
~Cat()
{
cout<< "---------------------猫猫析构函数-----释放内存" <<endl;
delete str;
}
void Say()
{
cout<< "喵喵喵~~~" <<endl;
}
};
这样写在语法上是没有问题的,但是如果子类动态的申请内存,并且是在子类析构函数中释放的内存,则可能会导致内存泄漏,如下面代码:
//用数组来存储一组动物
Animal *animals[2];
Animal *dog = new Dog();
Animal *cat = new Cat();
animals[0] = dog;
animals[1] = cat;
for(int i = 0; i< 2;i++)
{
animals[i]->Say();
}
for(int i = 0; i< 2;i++)
{
delete animals[i];
}
运行:
---------------------动物构造函数-----申请内存
---------------------狗子构造函数-----申请内存
---------------------动物构造函数-----申请内存
---------------------猫猫构造函数-----申请内存
汪汪汪~~~
喵喵喵~~~
---------------------动物析构函数-----释放内存
---------------------动物析构函数-----释放内存
可以看到子类和父类的构造函数都有调用,但是子类的析构函数都没有调用到,这样就必然会出问题,因为在子类的构造中我们申请了内存,却没有释放掉,这样就会导致内存泄漏。
除非改成以下代码:
Animal *animals[2];
Animal *dog = new Dog();
Animal *cat = new Cat();
animals[0] = dog;
animals[1] = cat;
for(int i = 0; i< 2;i++)
{
animals[i]->Say();
if(i == 0)
{
delete (Dog*)animals[i];
}
else if(i == 1)
{
delete (Cat*)animals[i];
}
}
但是这样就要求在父类中要清楚的知道每个子类具体是什么子类并且强制转换成子类才可以。往往在实际的开发过程中,子类的扩展是随意的,父类甚至都不知道子类的类名是什么,更别谈强制转换了,况且这样在删除的时候编码也很不方便,那么这个时候 virtual 关键字就闪亮登场了。
virtual ,就是告诉你,老子是虚构的,不是真的,你要想找真的,你就去找我儿子,儿子找不到,就去我儿子的儿子去找,总有一天你能找到真的。
使用起来也是非常的方便,由于在本例中,我们希望每个子类的析构函数都能正常被调用,所以我们将父类的虚构函数前加一个virtual关键字即可
完整程序如下:
#include <iostream>
#include <vector>
using namespace std;
class Animal {
public:
int id;
public:
virtual void Say()
{
cout<< "动物叫~~~" <<endl;
}
Animal()
{
cout<< "---------------------动物构造函数-----申请内存" <<endl;
}
// //如果直接delete父类 可能会导致内存泄漏
// ~Animal()
// {
// cout<< "---------------------动物析构函数-----释放内存" <<endl;
// }
//
//直接delete父类 也会去执行子类的析构函数
virtual ~Animal()
{
cout<< "---------------------动物析构函数-----释放内存" <<endl;
}
};
class Dog: public Animal{
public:
char * str;
Dog(){
cout<< "---------------------狗子构造函数-----申请内存" <<endl;
str = new char[1024*1024*8];
}
~Dog()
{
cout<< "---------------------狗子析构函数-----释放内存" <<endl;
delete str;
}
void Say()
{
cout<< "汪汪汪~~~" <<endl;
}
};
class Cat:public Animal{
public:
char * str;
Cat(){
cout<< "---------------------猫猫构造函数-----申请内存" <<endl;
str = new char[10000];
}
~Cat()
{
cout<< "---------------------猫猫析构函数-----释放内存" <<endl;
delete str;
}
void Say()
{
cout<< "喵喵喵~~~" <<endl;
}
};
int main(int argc, const char * argv[]) {
// insert code here...
//std::cout << "Hello, World!\n";
//用数组来存储一组动物
Animal *animals[2];
Animal *dog = new Dog();
Animal *cat = new Cat();
animals[0] = dog;
animals[1] = cat;
for(int i = 0; i< 2;i++)
{
animals[i]->Say();
}
for(int i = 0; i< 2;i++)
{
delete animals[i];
}
return 0;
}
运行:
---------------------动物构造函数-----申请内存
---------------------狗子构造函数-----申请内存
---------------------动物构造函数-----申请内存
---------------------猫猫构造函数-----申请内存
汪汪汪~~~
喵喵喵~~~
---------------------狗子析构函数-----释放内存
---------------------动物析构函数-----释放内存
---------------------猫猫析构函数-----释放内存
---------------------动物析构函数-----释放内存
可以看到父类和子类的析构函数,都执行了,避免了内存泄漏。
XCode提供了内存泄漏的检测工具
在程序运行的时候,点击左侧的第七个小图标,点Memory 再点右边的Profile in Instruments
然后再点击profile
在新弹出的窗口然后点击 Leaks即可查看当前程序的内存泄漏情况
绿色的对号表示没有内存泄漏,红色的叉则表示有内存泄漏
为了方便查看内存泄漏,我将程序父类改为非虚析构函数,并在运行时输入y就可以创建并删除一个狗子
#include <iostream>
#include <vector>
using namespace std;
class Animal {
public:
int id;
public:
virtual void Say()
{
cout<< "动物叫~~~" <<endl;
}
Animal()
{
cout<< "---------------------动物构造函数-----申请内存" <<endl;
}
//如果直接delete父类 可能会导致内存泄漏
~Animal()
{
cout<< "---------------------动物析构函数-----释放内存" <<endl;
}
// //直接delete父类 也会去执行子类的析构函数
// virtual ~Animal()
// {
// cout<< "---------------------动物析构函数-----释放内存" <<endl;
// }
};
class Dog: public Animal{
public:
char * str;
Dog(){
cout<< "---------------------狗子构造函数-----申请内存" <<endl;
str = new char[1024*1024*8];
}
~Dog()
{
cout<< "---------------------狗子析构函数-----释放内存" <<endl;
delete str;
}
void Say()
{
cout<< "汪汪汪~~~" <<endl;
}
};
class Cat:public Animal{
public:
char * str;
Cat(){
cout<< "---------------------猫猫构造函数-----申请内存" <<endl;
str = new char[10000];
}
~Cat()
{
cout<< "---------------------猫猫析构函数-----释放内存" <<endl;
delete str;
}
void Say()
{
cout<< "喵喵喵~~~" <<endl;
}
};
int main(int argc, const char * argv[]) {
string input;
while(true)
{
cout<< "是否继续创建狗子(y/n)"<<endl;
cin>>input;
if(input == "y")
{
Animal *dog = new Dog();;
dog->Say();
delete dog;
}
else
{
break;
}
}
return 0;
}
这样在内存检测窗口弹出以后,我们切回Xcode,按y创建一个狗子看是否有内存泄漏
在检测工具中我们看到了内存泄漏的提示,并且给出了内存泄漏的具体地址和大小,这个大小和狗子在构造函数中申请的内存如出一辙,大小匹配,是狗子内存泄漏无疑了,但狗子表示自己也很冤枉,因为析构函数明明有释放,都怪这个Animal父类不给力啊。
所以说,虚析构函数用得好,子类释放内存跑不了