2.为什么析构函数一般写成虚函数
2.为什么析构函数一般写成虚函数
在C++实现多态里,有一个关于 析构函数的重写问题:基类中的析构函数如果是虚函数,那么派生类的析构函数就重写了基类的析构函数。这里他们的函数名不相同,看起来违背了重写的规则,但实际上编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统一处理成destructor。那么为什么要把基类中的析构函数写成虚函数呢?
当使用多态特性,让基类指针指向派生类对象时,如果析构函数不是虚函数,通过基类指针销毁派生类对象时,会调用静态绑定的析构函数,也就是基类的析构函数,从而只能销毁属于基类的元素,导致派生类析构不完全,程序就会出现资源泄露或未定义行为。
当派生类中不存在使用动态资源或其他自定义析构行为时,可以不写为虚析构函数,来提高程序效率。但为了程序的可扩展性和健壮性,在使用多态特性时,一般都建议将基类的析构函数定义为虚函数。
在 C++ 中,只需要在基类中定义虚析构函数,派生类会自动继承这个虚属性。也就是说,如果基类的析构函数被声明为虚函数,那么所有派生类的析构函数都将自动成为虚函数。
如果你在派生类中显式地声明析构函数,无论你是否将其声明为虚函数,它都将是虚函数。因此,在派生类中声明虚析构函数并非必要,但是为了代码的清晰性,很多开发者仍然会在派生类中显式地声明虚析构函数。
这样的话,当我们看到派生类的代码时,我们就能立刻知道其析构函数是虚函数,这使得代码更易于理解。这也是一种被称为 "programming by contract" 的编程习惯,意味着类的设计者通过接口明确地表明了类的使用方式。
总结来说,基类的析构函数声明为虚函数后,派生类的析构函数自动成为虚函数,不论是否显式声明为虚函数。但为了代码清晰,有时候我们仍然会在派生类中显式地声明虚析构函数。
举个例子:
- 当基类析构函数不是虚函数时:
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<string>
using namespace std;
class A
{
public:
A() { cout << "A的构造" << endl; }
~A() { cout << "A的析构" << endl; }
void Work()
{
cout << "A工作" << endl;
}
};//基类
class B :public A
{
public:
B() { cout << "B的构造" << endl; }
~B() { cout << "B的析构" << endl; }
void Work() { cout << "B工作" << endl; }
}; //派生类
int main()
{
A* p = new B; //派生类对象赋给基类指针
p->Work();//此时调用的是基类的成员函数,因为基类的成员函数覆盖了派生类的同名成员函数
delete p;
system("pause");
return EXIT_SUCCESS;
}
输出:
A的构造
B的构造
A工作
A的析构
请按任意键继续. . .
可以看到在delete p的时候只调用了基类A的析构函数,并没有调用派生类B的析构函数,导致内存释放并不完全,出现内存泄漏的问题。
- 然后将基类析构函数写为虚函数时
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<string>
using namespace std;
class A
{
public:
A() { cout << "A的构造" << endl; }
virtual ~A() { cout << "A的析构" << endl; }
void Work()
{
cout << "A工作" << endl;
}
};//基类
class B :public A
{
public:
B() { cout << "B的构造" << endl; }
~B() { cout << "B的析构" << endl; } //在派生类中重写的成员函数可以不加virtual关键字
void Work() { cout << "B工作" << endl; }
};//派生类
int main()
{
A* p = new B; //派生类对象赋给基类指针
p->Work();//此时调用的是基类的成员函数,因为基类的成员函数覆盖了派生类的同名成员函数
delete p;
system("pause");
return EXIT_SUCCESS;
}
输出:
A的构造
B的构造
A工作
B的析构
A的析构
请按任意键继续.
可以看到这次在delete p的时候调用了派生类的析构函数,因为在调用派生类的析构函数后会自动调用基类的析构函数,这样整个派生类的对象被完全释放。
另外上面两个过程中我们发现执行 "p->Work();" 时,也就是p在调用同名成员函数的时候,调用的始终是基类的成员函数,这是因为基类的成员函数覆盖了派生类的同名成员函数,如果想要调用派生类的成员函数,同样将Work()设置为虚函数即可。
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<string>
using namespace std;
class A
{
public:
A() { cout << "A的构造" << endl; }
virtual ~A() { cout << "A的析构" << endl; }
virtual void Work()
{
cout << "A工作" << endl;
}
};
class B :public A
{
public:
B() { cout << "B的构造" << endl; }
~B() { cout << "B的析构" << endl; }
void Work() { cout << "B工作" << endl; }
};
int main()
{
A* p = new B; //派生类指针转化成基类指针
p->Work();
delete p;
system("pause");
return EXIT_SUCCESS;
}
输出:
A的构造
B的构造
B工作
B的析构
A的析构
请按任意键继续. . .
内存切割:
参考: