以前没太注意基类的C++的析构函数是否为虚函数的问题,今天在拷贝一个教程的例子下来跑的时候报错了,后面发现该例子中的析构函数都没写,报了一个错,等下再说这个错误。
先看看基类析构函数为虚析构函数的应用场景:
一个基类指针指向子类,当删除这个基类指针时,在基类的析构函数为虚函数前提下,此时会自动调用子类的析构函数,释放子类所有内存的目的,防止内存泄漏。
这种基类指针指向子类的方式,在设计模式中非常常见。
我们通过例子来体会这段话:
例一:
#include <iostream>
using namespace std;
class Apple
{
public:
Apple(){}
~Apple()
{
std::cout << "delete Apple" << std::endl;
}
};
// 基类
class Base {
public:
Base() {};
virtual ~Base() { // 基类的析构函数是虚函数!
cout << "delete Base\n";
};
void DoSomething() {
cout << "Do Something in class Base!\n";
};
};
// 派生类
class Derived : public Base {
public:
Derived() {};
~Derived() {
cout << "delete Derived\n";
};
void DoSomething() {
cout << "Do Something in Derived\n";
};
private:
Apple m_apple;
};
int main()
{
Base *b = new Derived();
b->DoSomething(); // 重写DoSomething函数
delete b;
system("pause");
return 0;
}
运行结果:
Do Something in class Base!
delete Derived
delete Apple
delete Base
请按任意键继续. . .
此时Derived中的析构函数被调用了,它所拥有的Apple也被释放了,一切看起来很好,没有内存泄漏。
但是如果Base的析构函数不是虚析构,而只是普通析构函数呢?
测试一下:
例二:(在例一的代码上修改,只将Base的析构函数前面的virtual去掉)
// 基类
class Base {
public:
Base() {};
~Base() { // 基类的析构函数是虚函数!
cout << "delete Base\n";
};
void DoSomething() {
cout << "Do Something in class Base!\n";
};
};
其余代码不变,运行结果:
Do Something in class Base!
delete Base
请按任意键继续. . .
Derived中的析构函数没有被调用,它拿着的Apple没有被释放,内存泄漏了。
其实虚析构函数也是虚函数,基类的指针指向子类时,调用虚函数的话,如果该虚函数在子类中有被重写(不管是自己重写的,还是系统自动添加的),那么子类的虚函数就会被调用。
我们还是通过例子来体会这句话:
例三:
#include <iostream>
using namespace std;
class Apple
{
public:
Apple(){}
~Apple()
{
std::cout << "delete Apple" << std::endl;
}
};
// 基类
class Base {
public:
Base() {};
virtual ~Base() { // 基类的析构函数是虚函数!
cout << "delete Base\n";
};
void DoSomething() {
cout << "Do Something in class Base!\n";
};
};
// 派生类
class Derived : public Base {
public:
Derived() {};
//~Derived() {
// cout << "delete Derived\n";
//};
void DoSomething() {
cout << "Do Something in Derived\n";
};
private:
Apple m_apple;
};
int main()
{
Base *b = new Derived();
b->DoSomething(); // 重写DoSomething函数
delete b;
system("pause");
return 0;
}
运行结果:
Do Something in class Base!
delete Apple
delete Base
请按任意键继续. . .
编译器自动帮我们添加了子类的虚构函数,而且是虚析构函数(为什么?因为基类的析构函数是virtual,子类的析构函数自然就是虚析构函数了)
这次内存也没泄漏,一切运行正常。
等等,还有一种情况呢,就是子类的指针指向子类,这时候就不用怀疑了,但是为了验证,我们还是测试一下。
例四:
#include <iostream>
using namespace std;
class Apple
{
public:
Apple(){}
~Apple()
{
std::cout << "delete Apple" << std::endl;
}
};
// 基类
class Base {
public:
Base() {};
virtual ~Base() { // 基类的析构函数是虚函数!
cout << "delete Base\n";
};
void DoSomething() {
cout << "Do Something in class Base!\n";
};
};
// 派生类
class Derived : public Base {
public:
Derived() {};
~Derived() {
cout << "delete Derived\n";
};
void DoSomething() {
cout << "Do Something in Derived\n";
};
private:
Apple m_apple;
};
int main()
{
Derived *d = new Derived();
d->DoSomething(); // 重写DoSomething函数
delete d;
system("pause");
return 0;
}
运行结果:
Do Something in Derived
delete Derived
delete Apple
delete Base
请按任意键继续. . .
例五:(在例四基础上修改)
int main()
{
{
Derived derived;
derived.DoSomething(); // 重写DoSomething函数
}
system("pause");
return 0;
}
运行结果:
Do Something in Derived
delete Derived
delete Apple
delete Base
请按任意键继续. . .
例四和例五就是我们常用的,子类调用子类的析构函数,子类析构过程是:先调用基类的析构函数,再执行子类析构函数。这时候也不会有内存泄漏。
小计一下:
1.在基类中析构函数建议加上virtual关键字,使之成为虚析构函数,这样在基类指针指向子类时,删除该基类指针才能调用子类析构函数,不造成内存泄漏。
2.当某个类没有子类时,析构函数当然可以不用虚析构函数了。
另外虚函数的写法是基类中加virtual,就像例一种的virtual ~Base();一样,子类中的析构函数不用加,这是虚函数的一种通常写法。
另外c++11中起,子类的重写基类的虚函数最好加上override,否则编译器会有警告。
就像下面这样:
class Base {
public:
Base() {};
virtual ~Base() { // 基类的析构函数是虚函数!
cout << "delete Base\n";
};
void DoSomething() {
cout << "Do Something in class Base!\n";
};
};
// 派生类
class Derived : public Base {
public:
Derived() {};
~Derived() override{
cout << "delete Derived\n";
};
void DoSomething() {
cout << "Do Something in Derived\n";
};
private:
Apple m_apple;
};
参考:
https://blog.youkuaiyun.com/cdlwhm1217096231/article/details/90604166
https://www.cnblogs.com/albizzia/p/8979078.html