這篇來學習多態中可能會發生內存泄漏和解決辦法,就要使用到虛析構函數和純虛析構函數。先不介紹概念,肯定和前面學構造函數和析構函數中的析構函數有關係。先通過引出問題,然後介紹這兩個概念和特點。
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)都需要有具體的函數實現
虛析構和純虛析構區別:
如果純虛析構,該類屬於抽象類,無法實例化對象