现在的位置: 首页 > 综合 > 正文

C++ 中析构函数为什么要求是虚的

2013年08月17日 ⁄ 综合 ⁄ 共 1653字 ⁄ 字号 评论关闭

这是因为,通过基类指针来销毁派生类对象这个行为,当基类没有虚析构函数时会产生问题。我们知道删除指针对象是没有问题的,指针对象的析构函数会正确调用,但仅限于指针的类型所表示的对象大小。如果以一个基类指针指向其派生类,删除这个基类指针只能删除基类对象部分,而不能删除整个派生类对象,原因是通过基类指针无法访问派生类的析构函数。
但是,如果像其它虚函数一样,基类的析构函数也是虚的,那么派生类的析构函数也必然是虚的,删除基类指针时,它就会通过虚函数表找到正确的派生类析构函数并调用它,从而正确析构整个派生类对象。

这点就像我们生成一个动态数组,然后释放它的空间一样。是整个数组而不是其中一个元素的空间要释放。如:

char *itsString = new char [2];
delete [ ] itsString; 

要加上 [ ] ,表示删除整个数组,如果不加上只会删除 itsString 的第一个元素。
delete 这里的删除指的是 释放所占用的内存空间。

打个比方,如果基类指针相当于 itsString ,那么整个类就像 [ ] itsString 。而我们真正想析构的是整个对象而不是对象的一部分。

我们来看看代码:

#include <iostream>
using namespace std;
class CBase
{
public:
	CBase() { cout << "CBase Constructor...\n"; }
	virtual ~CBase() { cout << "CBase Destructor...\n"; } //指定基类析构函数为虚的
	virtual void display() { cout << "Hello, World! CBase \n"; } //虚函数
	void Hello() { cout << "Call CBase Hello.\n"; } //非虚函数
};
class CDerived: public CBase
{
public:
	CDerived() { cout << "CDerived Constructor...\n"; }
	~CDerived() { cout << "CDerived Destructor...\n"; } //这里也是虚析构函数,因为基类中析构函数是虚的
	void display() { cout << "Hello, World! CDerived \n"; } //虚函数,覆盖基类中的虚函数。
	void Hello() { cout << "Call CDerived Hello.\n"; } //非虚函数
};
void main()
{
	CDerived Derived; //栈中对象
	CBase *pBase = new CDerived; // 以基类指针指向派生类对象。这里是堆中对象
	
	cout << "Derived.Hello(); \n\t"; 
	Derived.Hello();
	cout << "pBase->Hello(); \n\t"; // 因为 Hello() 为 非虚函数,只能访问基类中的方法。
	pBase->Hello();
	cout << "Derived.display(); \n\t";
	Derived.display();
	cout << "pBase->display(); \n\t"; // 因为虚函数的关系,派生类中的方法被正确调用。
	pBase->display();
	cout << "delete pBase; \n\t"; 
	/*
	问题就在这里,如果基类的析构函数不是虚的,那么这里只能析构 CBase 对象,和指针的类型相同。
	基类使用虚析构函数后,基类指针才可以(通过虚函数表)访问派生类的虚析构函数,调用派生类的析构函数析构派生类本身,可以看到整个派生类依次被析构掉了。
	*/
	delete pBase; // 基类是虚析构函数,进而派生类析构函数也是虚的,整个派生类被正确析构。
	
	cout << "----------------\n";
}

下面,我在这里提一下另一个关注的问题:

为什么构造函数不能是虚的?

原因是构造自己时,对象还不存在。虚函数需要有虚函数表,但这个表因为在构造阶段是不存在的,至少还没分配内存,无法实现定义要求。

【上篇】
【下篇】

抱歉!评论已关闭.