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

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

2013年09月19日 ⁄ 综合 ⁄ 共 2056字 ⁄ 字号 评论关闭

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

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

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

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

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

         一般情况下类的析构函数里面都是释放内存资源,而析构函数不被调用的话就会造成内存泄漏。我想所有的C++程序员都知道这样的危险性。当然,如果在析构函数中做了其他工作的话,那你的所有努力也都是白费力气。
         所以,文章开头的那个问题的答案就是--这样做是为了当用一个基类的指针删除一个派生类的对象时,派生类的析构函数会被调用。
        当然,并不是要把所有类的析构函数都写成虚函数。因为当类里面有虚函数的时候,编译器会给类添加一个虚函数表,里面来存放虚函数指针,这样就会增加类的存储空间。所以,只有当一个类被用来作为基类的时候,才把析构函数写成虚函数。

我们来看看代码:

#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";
}

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

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

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

抱歉!评论已关闭.