1. 概述
類的解構函式執行與建構函式相反的操作,當物件結束其生命週期,程式就會自動執行解構函式:
class ImageEx
{
public:
ImageEx()
{
cout << "Execute the constructor!" << endl;
}
~ImageEx()
{
cout << "Execute the destructor!" << endl;
}
};
int main()
{
ImageEx imageEx;
return 0;
}
那麼同樣的問題來了,為什麼要有解構函式呢?
2. 詳論
2.1. 物件生命週期
在經典C++中,需要通過new/delete來手動管理動態記憶體。如果我們在類中申請一個動態陣列,並且通過自定義的函式Release()來釋放它:
class ImageEx
{
public:
ImageEx()
{
cout << "Execute the constructor!" << endl;
data = new unsigned char[10];
}
~ImageEx()
{
cout << "Execute the destructor!" << endl;
}
void Release()
{
delete[] data;
data = nullptr;
}
private:
unsigned char * data;
};
int main()
{
{
ImageEx imageEx;
imageEx.Release();
}
return 0;
}
那麼,當類物件離開作用域,結束生命週期之前,就必須顯示呼叫一次成員函式Release(),否則就會造成記憶體洩漏:物件在呼叫解構函式之後,只會銷燬資料成員data本身,而不是其指向的記憶體。
那麼,一個合理的實現是,將成員函式Release()放入到解構函式:
class ImageEx
{
public:
ImageEx()
{
cout << "Execute the constructor!" << endl;
data = new unsigned char[10];
}
~ImageEx()
{
Release();
cout << "Execute the destructor!" << endl;
}
private:
unsigned char * data;
void Release()
{
delete[] data;
data = nullptr;
}
};
int main()
{
{
ImageEx imageEx;
}
return 0;
}
這樣,當類物件離開作用域,結束生命週期之前,就自動通過解構函式,實現了動態陣列的釋放。好處是顯而易見的:實現了類似於內建資料型別物件的生命週期管理,我們可以像使用內建資料型別物件一樣使用類物件。這也體現了前文《物件導向程式設計(C++篇1)——引言》中提到的設計原則:類是抽象的自定義資料型別。
2.2. 不一定需要顯式析構
在一些現代高階程式語言(C#、Java、Javascript)中,已經不用去手動管理動態記憶體,取而代之的,是其與作業系統的中介軟體(.net,jvm,瀏覽器)的GC(垃圾回收)機制。而在現代C++中,提倡通過智慧指標(std::shared_ptr、std::unique_ptr、std::weak_ptr)來管理動態記憶體;對於動態陣列,則使用標準容器std::vector則更好。在兩者的內部都實現了前文提到的物件生命週期管理,在離開作用域後,通過解構函式自動釋放管理的記憶體,無需再手動進行回收。
那麼,一個顯而易見的推論就出來了,如果我們在類中使用智慧指標或者vector容器來替代new/delete管理動態記憶體,是不是就可以不用解構函式了?嚴格來說,是不用顯式使用解構函式:
class ImageEx
{
public:
ImageEx():
data(10)
{
cout << "Execute the constructor!" << endl;
}
private:
std::vector<unsigned char> data;
};
int main()
{
ImageEx imageEx;
return 0;
}
實際上,並不是這個類不存在解構函式,而是編譯器會為它生成一個合成的解構函式,在這個解構函式體中,什麼也不用做。因為類中的動態記憶體,已經交由std::vector容器來管理。當類物件離開作用域呼叫解構函式之後,會銷燬這個std::vector容器資料成員,進而觸發其解構函式,釋放其管理的記憶體。
2.3. 析構的必要性
根據上一節內容,不一定需要顯式析構。因為現代C++的一些機制能夠幫你自動管理動態記憶體。但是解構函式還是必要的,這是由於C++語言本身的性質決定的。作為C語言大部分內容的超集,需要相容C語言手動管理記憶體的特性。更重要的是,現代作業系統幾乎全部由C語言編寫,與底層的互動不可避免的需要手動使用動態記憶體管理。
3. 總結
所以我們就能理解了,C++這門語言的設計哲學就是就是這樣:既想要C語言的高效能,也想要高階語言高度抽象的特性。如果我們必須相容C語言底層設計,那我們最好使用解構函式釋放動態記憶體;否則多數情況下,我們應該使用智慧指標或者stl容器來管理動態記憶體,從而避免顯示使用解構函式。