# [11] 析構函數
## FAQs in section [11]:
* [11.1] 析構函數做什么?
* [11.2] 局部對象析構的順序是什么?
* [11.3] 數組中的對象析構順序是什么?
* [11.4] 我能重載類的析構函數嗎?
* [11.5] 我可以對局部變量顯式調用析構函數嗎?
* [11.6] 如果我要一個局部對象在其被創建的代碼塊的 } 之前被析構,如果我真的想這樣,能調用其析構函數嗎?
* [11.7] 好,好;我不顯式調用局部對象的析構函數;但如何處理上面那種情況?
* [11.8] 如果我無法將局部對象包裹于人為的塊中,怎么辦?
* [11.9] 如果我是用 `new` 分配對象的,可以顯式調用析構函數嗎?
* [11.10] 什么是“定位放置 `new`(placement `new` )”,為什么要用它?
* [11.11] 編寫析構函數時,需要顯式調用成員對象的析構函數嗎?
* [11.12] 當我寫派生類的析構函數時,需要顯式調用基類的析構函數嗎?
* [11.13] 當析構函數檢測到錯誤時,可以拋出異常嗎?
## 11.1 析構函數做什么?
析構函數為對象舉行葬禮。
析構函數用來釋放對象所分配的資源。舉例來說,`Lock` 類可能鎖定了一個信號量,那么析構函數將釋放該信號量。最常見的例子是,當構造函數中使用了`new`,那么析構函數則使用`delete`。
析構函數是“準備后事”的成員函數。經常縮寫成“dtor”。
## 11.2 局部對象析構的順序是什么?
與構造函數反序:先被構造的,被后析構。
以下的例子中,`b`的析構函數會被首先執行,然后是 `a` 的析構函數:
```
?void?userCode()
?{
???Fred?a;
???Fred?b;
???//?...
?}
```
## 11.3 數組中的對象析構順序是什么?
與構造函數反序:先被構造的,后被析構。
以下的例子中,析構的順序是`a[9]`, `a[8]`, ..., `a[1]`, `a[0]`:
```
?void?userCode()
?{
???Fred?a[10];
???_//?..._
?}
```
## 11.4 我能重載類的析構函數嗎?
不行。
類只能有一個析構函數。`Fred` 類的析構函數能是`Fred::~Fred()`。不帶任何參數,不返回任何東西(譯注:void也不行)。
由于你不會顯式地調用析構函數(是的,永遠不會),因此無論如何不能傳遞參數給析構函數。
## 11.5 我可以對局部變量顯式調用析構函數嗎?
不行!
在創建該局部對象的代碼塊的 `}` 處,析構函數會自動被調用。這是語言所保證的;自動發生。沒有辦法阻止它。而兩次調用同一個對象的析構函數,你得到的真是壞的結果!砰!你完蛋了!
## 11.6 如果我要一個局部對象在其被創建的代碼塊的 `}`之前被析構,如果我真的想這樣,能調用其析構函數嗎?
不行!詳見 [前一個FAQ].
假設析構 `File` 對象的作用是關閉文件。現在假定你有一個 `File`類的對象 `f`,并且你想 `File` `f` 在 `f` 對象的作用范圍結束(也就是 `}` )之前被關閉:
```
?void?someCode()
?{
???File?f;
???//?...?[這些代碼在 f 打開的時候執行]?...
//?<—?希望在此處關閉 f
//?...?[這些代碼在 f 關閉后執行]?...
?}
```
對這個問題有一個簡單的解決方案。但現在請記住:_不要顯式調用析構函數!_
## 11.7 好,好;我不顯式調用局部對象的析構函數;但如何處理上面那種情況?
內容詳見 [前一個 FAQ].
只要將局部對象的生命期長度包裹于一個人為的 `{`...`}` 塊中:
```
?void?someCode()
?{
???{
?????File?f;
?????//?...?[這些代碼在 f 打開的時候執行]?...
???}
? //?^—?f?的析構函數在此處會被自動調用!
//?...?[這些代碼在 f 關閉后執行]?...
?}
```
## 11.8 如果我無法將局部對象包裹于人為的塊中,怎么辦?
大多數時候,你可以通過將局部對象包裹于人為的`{`...`}`塊中,限制其生命期。但如果由于一些原因無法這樣做,則增加一個模擬析構函數作用的成員函數。但_不要調用析構函數本身_!
例如,`File`類的情況下,可以添加一個`close()`方法。典型的析構函數只是調用`close()`方法。注意`close()`方法需要標記 `File` 對象,以便后續的調用不會再次關閉一個已經關閉的文件。舉例來說,可以將一個`fileHandle_`數據成員設置為 -1,并且在開頭檢查`fileHandle_`是否已經等于-1:
```
?class?File?{
?public:
???void?close();
???~File();
???//?...
?private:
???int?fileHandle_;???//?當且僅當文件打開時 fileHandle_?>=?0
?};
?File::~File()
?{
???close();
?}
?void?File::close()
?{
???if?(fileHandle_?>=?0)?{
?????//?...?[執行一些操作-系統調用來關閉文件]?...
?????fileHandle_?=?-1;
???}
?}
```
注意其他的 `File`方法可能也需要檢查`fileHandle_`是否為 -1(也就是說,檢查文件是否被關閉了)。
還要注意任何沒有實際打開文件的構造函數,都應該將`fileHandle_`設置為 -1。
## 11.9 如果我是用`new`分配對象的,可以顯式調用析構函數嗎?
可能不行。
除非你使用定位放置 `new`,否則應該 `delete` 對象而不是顯式調用析構函數。例如,假設通過一個典型的 `new` 表達式分配一個對象:
```
Fred*?p?=?new?Fred();
```
那么,當你`delete`它時,析構函數 `Fred::~Fred()` 會被調用:
```
delete?p;??//?自動調用?p->~Fred()
```
由于顯式調用析構函數不會釋放 `Fred` 對象本身分配的內存,因此不要這樣做。記住:`delete?p` 做了兩件事情:調用析構函數,回收內存。
## 11.10 什么是“定位放置`new`(placement `new`)”,為什么要用它 ?
定位放置`new`(placement `new`)有很多作用。最簡單的用處就是將對象放置在內存中的特殊位置。這是依靠 `new`表達式部分的指針參數的位置來完成的:
```
?#include?<new>????????//?必須?#include?這個,才能使用?"placement?new"
?#include?"Fred.h"?????//?class?Fred 的聲明
?void?someCode()
?{
???char?memory[sizeof(Fred)];?????//?Line?#1
???void*?place?=?memory;??????????//?Line?#2
???Fred*?f?=?new(place)?Fred();???//?Line?#3?(詳見以下的“危險”)
//?The?pointers?f?and?place?will?be?equal
//?...
?}
```
Line #1 在內存中創建了一個`sizeof(Fred)`字節大小的數組,足夠放下 `Fred` 對象。Line #2 創建了一個指向這塊內存的首字節的`place`指針(有經驗的 C 程序員會注意到這一步是多余的,這兒只是為了使代碼更明顯)。Line #3 本質上只是調用了構造函數 `Fred::Fred()`。`Fred`構造函數中的`this`指針將等于`place`。因此返回的 `f` 將等于`place`。
建議:萬不得已時才使用“placement `new`”語法。只有當你真的在意對象在內存中的特定位置時才使用它。例如,你的硬件有一個內存映象的 I/O計時器設備,并且你想放置一個`Clock`對象在那個內存位置。
危險:你要獨自承擔這樣的責任,傳遞給“placement `new`”操作符的指針所指向的內存區域必須足夠大,并且可能需要為所創建的對象進行邊界調整。編譯器和運行時系統都不會進行任何的嘗試來檢查你做的是否正確。如果 `Fred`類需要將邊界調整為4字節,而你提供的位置沒有進行邊界調整的話,你就會親手制造一個嚴重的災難(如果你不明白“邊界調整”的意思,那么就不要使用placement `new`語法)。
你還有析構放置的對象的責任。這通過顯式調用析構函數來完成:
```
?void?someCode()
?{
???char?memory[sizeof(Fred)];
???void*?p?=?memory;
???Fred*?f?=?new(p)?Fred();
???//?...
???f->~Fred();???//?顯式調用定位放置的對象的析構函數
?}
```
這是顯式調用析構函數的唯一時機。
## 11.11 編寫析構函數時,需要顯式調用成員對象的析構函數嗎?
不!永遠不需要顯式調用析構函數(除了定位放置 `new`的情況)。
類的析構函數(不論你是否顯式地定義了)自動調用成員對象的析構函數。它們以出現在類聲明中的順序的反序被析構。
```
?class?Member?{
?public:
???~Member();
???//?...
?};
?class?Fred?{
?public:
???~Fred();
???_//?..._
?private:
???Member?x_;
???Member?y_;
???Member?z_;
?};
?Fred::~Fred()
?{
???//?編譯器自動調用?z_.~Member()
//?編譯器自動調用?y_.~Member()
//?編譯器自動調用?x_.~Member()
?}
```
## 11.12 當我寫派生類的析構函數時,需要顯式調用基類的析構函數嗎?
不!永遠不需要顯式調用析構函數(除了定位放置 `new`的情況)。
派生類的析構函數(不論你是否顯式地定義了)自動調用基類子對象的析構函數。基類在成員對象之后被析構。在多重繼承的情況下,直接基類以出現在繼承列表中的順序的反序被析構。
```
?class?Member?{
?public:
???~Member();
???//?...
?};
?class?Base?{
?public:
???virtual?~Base();?????//?虛析構函數
//?...
?};
?class?Derived?:?public?Base?{
?public:
???~Derived();
???//?...
?private:
???Member?x_;
?};
?Derived::~Derived()
?{
???//?編譯器自動調用?x_.~Member()
//?編譯器自動調用?Base::~Base()
?}
```
注意:虛擬繼承的順序相關性是多變的。如果你在一個虛擬繼承層次中依賴于其順序相關性,那么你需要比這個FAQ更多的信息。
## 11.13 當析構函數檢測到錯誤時,可以拋出異常嗎?
謹防!!! 詳見 該 FAQ。
- C++ FAQ Lite
- [1] 復制許可
- [2] 在線站點分發本文檔
- [3] C++-FAQ-Book 與 C++-FAQ-Lite
- [6] 綜述
- [7] 類和對象
- [8] 引用
- [9] 內聯函數
- [10] 構造函數
- [11] 析構函數
- [12] 賦值算符
- [13] 運算符重載
- [14] 友元
- [15] 通過 &lt;iostream&gt; 和 &lt;cstdio&gt;輸入/輸出
- [16] 自由存儲(Freestore)管理
- [17] 異常和錯誤處理
- [18] const正確性
- [19] 繼承 — 基礎
- [20] 繼承 — 虛函數
- [21] 繼承 — 適當的繼承和可置換性
- [22] 繼承 — 抽象基類(ABCs)
- [23] 繼承 — 你所不知道的
- [24] 繼承 — 私有繼承和保護繼承
- [27] 編碼規范
- [28] 學習OO/C++
- [31] 引用與值的語義
- [32] 如何混合C和C++編程
- [33] 成員函數指針
- [35] 模板 ?
- [36] 序列化與反序列化
- [37] 類庫