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的析构
请按任意键继续. . .

内存切割:

参考:

C++:基类析构函数为什么要定义为虚函数

posted @ 2023-08-03 08:08  CodeMagicianT  阅读(94)  评论(0编辑  收藏  举报