C++面向對象-26-虛析構和純虛析構

這篇來學習多態中可能會發生內存泄漏和解決辦法,就要使用到虛析構函數和純虛析構函數。先不介紹概念,肯定和前面學構造函數和析構函數中的析構函數有關係。先通過引出問題,然後介紹這兩個概念和特點。

 

1.多態基本代碼

在前面例子我們可寫出一下代碼,也沒有什麼問題

#include <iostream>
#include <string>
using namespace std;

class Animal
{
public:
    virtual void speak() = 0;

};

class Cat : public Animal
{
public:
    virtual void speak()
    {
        cout << "小貓在說話" << endl;
    }

};


void test01()
{
   Animal * ani = new Cat;
   ani->speak();
   delete ani;
}

int main()
{
    test01();
    system("pause");
}

輸出的內容是:小貓在說話。

 

2.子類添加成員屬性,打印出構造和析構順序

在子類Cat中,我們給添加一個成員屬性,例如name,speak函數就打印  波斯貓在說話。然後補齊父類和子類中構造函數和析構函數中打印語句,方便測試觀察調用順序。

#include <iostream>
#include <string>
using namespace std;

class Animal
{
public:
    Animal()
    {
        cout << "Animal中調用構造函數" << endl;
    }

    virtual void speak() = 0;
    ~Animal()
    {
        cout << "Animal中調用析構函數" << endl;
    }
};

class Cat : public Animal
{
    
public:
    Cat(string name)
    {
        cout << "Cat中調用構造函數" << endl;
        m_Name = new string(name);
    }

    virtual void speak()
    {
        cout << *m_Name << "在說話" << endl;
    }
    string *m_Name;

    ~Cat()
    {
        if(m_Name != NULL)
        {
            cout << "Cat中調用析構函數" << endl;
            delete m_Name;
            m_Name = NULL;
        }
    }

};


void test01()
{
   Animal * ani = new Cat("波斯貓");
   ani->speak();
   delete ani;
}

int main()
{
    test01();
    system("pause");
}

上面這段代碼是前面學習過的析構函數和構造函數,加上剛剛學純虛函數。

前面學習繼承的時候,學習過了構造函數和析構函數調用順序。

關於構造函數,先調用父類的構造函數,然後調用子類的構造函數,可以這麼記憶,現有老爹纔有兒子。

關於析構函數,正好反過來,先調用子類析構函數,然後調用父類的析構函數。

 

運行這段代碼

看起來正常,認真一看,覺得少了什麼。就是少了子類析構。

 

3.解決子類無法析構的問題

這個問題產生的原因是,我們在test01()函數中,第一行,父類指針指向子類對象。父類指針在析構的時候,不會調用子類中析構函數,導致子類如果有堆區屬性,出現了內存泄漏。

要解決這個問題很簡單,就是把父類中析構函數改成虛析構。一個虛函數,只不過這裏是構造函數類型的虛函數,叫虛析構函數。

#include <iostream>
#include <string>
using namespace std;

class Animal
{
public:
    Animal()
    {
        cout << "Animal中調用構造函數" << endl;
    }

    virtual void speak() = 0;
    virtual ~Animal()
    {
        cout << "Animal中調用析構函數" << endl;
    }
};

class Cat : public Animal
{
    
public:
    Cat(string name)
    {
        cout << "Cat中調用構造函數" << endl;
        m_Name = new string(name);
    }

    virtual void speak()
    {
        cout << *m_Name << "在說話" << endl;
    }
    string *m_Name;

    ~Cat()
    {
        if(m_Name != NULL)
        {
            cout << "Cat中調用析構函數" << endl;
            delete m_Name;
            m_Name = NULL;
        }
    }

};


void test01()
{
   Animal * ani = new Cat("波斯貓");
   ani->speak();
   delete ani;
}

int main()
{
    test01();
    system("pause");
}

在上面14行代碼析構函數前面添加關鍵字virtual,運行

利用虛析構可以解決:父類指針釋放子類對象時不乾淨的問題

 

4.純虛析構

在3的基礎上,把父類中析構函數改成純虛析構,也就是析構函數沒有實現代碼

改完之後,其他代碼不變,運行會報錯。

上面是一個鏈接的錯誤,這裏純虛析構 需要有聲明也需要有實現。我們繼續改代碼如下

改成純虛析構函數後,Base類也變成一個抽象類,當然這裏前面就有純虛函數,本身就是抽象類。抽象類的特點就是不能被實例化。

 

總結:

多態使用時候,如果子類中有屬性開闢到堆區(new),那麼父類指針在釋放時無法調用到子類的析構代碼。
解決方法:將父類中的析構函數改成虛析構或者純虛析構

虛析構和純虛析構共性:
1)可解決父類指針釋放子類對象
2)都需要有具體的函數實現


虛析構和純虛析構區別:
如果純虛析構,該類屬於抽象類,無法實例化對象

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章