# Item 52: 如果編寫了 placement new,就要編寫 placement delete
作者:[Scott Meyers](http://aristeia.com/)
譯者:[fatalerror99 (iTePub's Nirvana)](mailto:fatalerror9999@hotmail.com?subject=Item%2049)
發布:[http://blog.csdn.net/fatalerror99/](http://blog.csdn.net/fatalerror99/)
在 C++ 動物園中,placement new 和 placement delete 并不是最常遇到的野獸,所以如果你和它們不熟也不必擔心。作為替代,回想一下 [Items 16](http://blog.csdn.net/fatalerror99/archive/2005/07/20/430119.aspx) 和 [17](http://blog.csdn.net/fatalerror99/archive/2005/07/21/431251.aspx),當你寫下一個這樣的 new 表達式,
```
Widget *pw = new Widget;
```
有兩個函數會被調用:一個是 operator new 用于分配內存,第二個是 Widget 的 default constructor(缺省構造函數)。
假設第一個調用成功,而第二個調用導致拋出一個 exception(異常)。這種情況下,第 1 步中完成的內存分配必須被撤銷。否則就是一個內存泄漏。客戶代碼不可能回收這些內存,因為,如果 Widget 的 constructor(構造函數)拋出一個 exception(異常),pw 根本就沒有被賦值。對于客戶來說無法得到指向應該被回收的內存的指針。所以撤銷第 1 步的職責必然落在了 C++ runtime system(C++ 運行時系統)的身上。
runtime system(運行時系統)恰當地調用與它在第 1 步中調用的 operator new 的版本相對應的 operator delete,但是只有在它知道哪一個 operator delete——可能有許多——最恰當的時候它才能做到這一點。如果你正在擺弄具有常規的 signatures(識別特征)的 new 和 delete 版本,這不成問題,因為常規的 operator new,
```
void* operator new(std::size_t) throw(std::bad_alloc);
```
對應常規的 operator delete:
```
void operator delete(void *rawMemory) throw();? // normal signature
??????????????????????????????????????????????? // at global scope
void operator delete(void *rawMemory,?????????? // typical normal
???????????????????? std::size_t size) throw(); // signature at class
??????????????????????????????????????????????? // scope
```
當你只使用 new 和 delete 的常規形式時,runtime system(運行時系統)找出知道如何撤銷 new 所做的事情的 delete 沒什么麻煩。然而,當你開始聲明 operator new 的非常規形式——帶有額外參數的形式的時候,which-delete-goes-with-this-new(哪一個 delete 和這個 new 配對)的問題就出現了。
例如,假設你編寫了一個 class-specific(類專用)的 operator new,它需要一個用于記錄分配信息的 ostream 的規格描述,而你又編寫了一個常規的 class-specific(類專用)的 operator delete:
```
class Widget {
public:
? ...
? static void* operator new(std::size_t size,????????????? // non-normal
??????????????????????????? std::ostream& logStream)?????? // form of new
??? throw(std::bad_alloc);
? static void operator delete(void *pMemory??????????????? // normal class-
????????????????????????????? std::size_t size) throw();?? // specific form
?????????????????????????????????????????????????????????? // of delete
? ...
};
```
這個設計是成問題的,但是在我們探究為什么之前,我們需要做一個簡要的術語說明。
當一個 operator new function 持有額外的參數(除了那個必要的 size_t 參數),這個 function 就被稱為 new 的 _placement_ 版本。前面那個 operator new 就是這樣一個 placement 版本。有一個特別有用的 placement new,它持有一個指針,這個指針指定了一個 object 被構造的位置。那個 operator new 如下:
```
void* operator new(std::size_t, **void *pMemory**) throw();?? // "placement
????????????????????????????????????????????????????????? // new"
```
new 的這個版本是 C++ 標準庫的一部分,只要 #include <new> 你就可以訪問它。需要指出,這個 new 用于 vector 內部,在 vector 的尚未使用的空間內創建 objects。它也是最初的 placement new。實際上,這就是這類函數被稱為 _placement new_ 的來歷。這就意味著術語 "placement new" 被賦予了更多的含義。大多數情況下,當人們談到 placement new,他們談的就是這個特定的函數,持有一個 void\* 類型的額外參數的 operator new。較少情況下,他們談的是持有額外參數的 operator new 的任意版本。根據上下文通常可以搞清楚任何曖昧,重要的是要了解到通用術語 "placement new" 意味著持有額外參數的 new 的任意版本,因為短語 "placement delete"(過一會兒我們就會遇到它)直接起源于它。
我們讓我們先返回到 Widget class 的 declaration(聲明),就是我說設計成問題的那個。麻煩就在于這個 class 會引發微妙的 memory leaks(內存泄漏)。考慮如下客戶代碼,在動態創建一個 Widget 時,它將在 cerr 記錄分配信息:
```
Widget *pw = new (std::cerr) Widget; // call operator new, passing cerr as
???????????????????????????????????? // the ostream; _this leaks memory_
???????????????????????????????????? // _if the Widget constructor throws_
```
重申一次,如果內存分配成功而 Widget constructor(構造函數)拋出一個 exception(異常),runtime system(運行時系統)有責任撤銷 operator new 所執行的分配。然而,runtime system(運行時系統)不能真正了解被調用的 operator new 版本是如何工作的,所以它自己無法撤銷那個分配。runtime system(運行時系統)轉而尋找一個和 operator new 持有相同數量和類型額外參數的 operator delete 版本,而且,如果它找到了,它將調用它。在當前情況下,operator new 持有一個 ostream& 類型的額外參數,所以相應的 operator delete 應該具有這樣的 signature(識別特征):
```
void operator delete(void *, **std::ostream&**) throw();
```
與 new 的 placement 版本類似,持有額外參數的 operator delete 版本被稱為 _placement deletes_。當前情況下,Widget 沒有聲明 operator delete 的 placement 版本,所以 runtime system(運行時系統)不知道如何撤銷所調用的 placement new 所做的事情。結果,它什么都不做。在本例中,如果 Widget constructor(構造函數)拋出一個 exception(異常),沒有 _operator delete_ 可以被調用!
規則很簡單:如果一個帶有額外參數的 operator new 沒有帶有同樣額外參數的 operator delete 相匹配,當一個由 new 生成的內存分配需要撤銷的時候沒有 operator delete 可以被調用。為了消除前面的代碼中的 memory leak(內存泄漏),Widget 需要聲明一個與 logging placement new 相對應的 placement delete:
```
class Widget {
public:
? ...
? static void* operator new(std::size_t size, std::ostream& logStream)
??? throw(std::bad_alloc);
? static void operator delete(void *pMemory) throw();
? **static void operator delete(void *pMemory, std::ostream& logStream)**
??? **throw();**
? ...
};
```
這樣改變之后,如果從下面這個語句的 Widget constructor(構造函數)中拋出一個 exception(異常),
```
Widget *pw = new (std::cerr) Widget;?? // as before, but no leak this time
```
相應的 placement delete 自動被調用,而這就讓 Widget 確保沒有內存被泄漏。
然而,考慮以下情況會發生什么,如果沒有拋出 exception(異常)(這是通常的情況)而我們的客戶代碼中又有一個 delete:
```
delete pw;??????????????????????????? // invokes the normal
????????????????????????????????????? // operator delete
```
就像注釋中所說的,這樣將調用常規 operator delete,而不是 placement 版本。只有在調用一個與 placement new 相關聯的 constructor(構造函數)時發生一個 exception(異常),placement delete 才會被調用。將 delete 施加于一個指針(諸如上面的 pw)絕對不會引起一個 delete 的 placement 版本的調用。絕對不會。
這就意味著為了預防所有與 new 的 placement 版本相關的 memory leaks(內存泄漏),你必須既提供常規 operator delete(用于構造過程中沒有拋出 exception(異常)時),又要提供一個持有與 operator new 相同的 extra arguments(額外參數)的 placement 版本(用于相反情況)。這樣,你就再也不會因為微妙的 memory leaks(內存泄漏)而睡不著覺了。好吧,至少是不會因為這里這些微妙的 memory leaks(內存泄漏)。
順便說一下,因為 member function(成員函數)的名字會覆蓋外圍的具有相同名字的函數(參見 [Item 33](http://blog.csdn.net/fatalerror99/archive/2005/09/13/479864.aspx)),你需要小心避免用 class-specific(類專用)的 news 覆蓋你的客戶所希望看到的其它 news(包括其常規版本)。例如,如果你有一個只聲明了一個 operator new 的 placement 版本的 base class(基類),客戶將發現 new 的常規形式對他們來說無法使用:
```
class Base {
public:
? ...
? static void* operator new(std::size_t size,?????????? // this new hides
??????????????????????????? std::ostream& logStream)??? // the normal
??? throw(std::bad_alloc);????????????????????????????? // global forms
? ...
};
Base *pb = new Base;??????????????????????? // error! the normal form of
??????????????????????????????????????????? // operator new is hidden
Base *pb = new (std::cerr) Base;??????????? // fine, calls Base's
??????????????????????????????????????????? // placement new
```
同樣,derived classes(派生類)中的 operator news 覆蓋 operator news 的全局和繼承來的版本的 operator new:
```
class Derived: public Base {?????????????????? // inherits from Base above
public:
? ...
? static void* operator new(std::size_t size)? // redeclares the normal
????? throw(std::bad_alloc);?????????????????? // form of new
? ...
};
Derived *pd = new (std::clog) Derived;???????? // error! Base's placement
?????????????????????????????????????????????? // new is hidden
Derived *pd = new Derived;???????????????????? // fine, calls Derived's
?????????????????????????????????????????????? // operator new
```
[Item 33](http://blog.csdn.net/fatalerror99/archive/2005/09/13/479864.aspx) 討論了這種名字覆蓋的需要考慮的細節,如果打算編寫內存分配函數,你要記住,在缺省情況下,C++ 在全局范圍提供如下形式的 operator new:
```
void* operator new(std::size_t) throw(std::bad_alloc);????? // normal new
void* operator new(std::size_t, void*) throw();???????????? // placement new
void* operator new(std::size_t,???????????????????????????? // nothrow new —
?????????????????? const std::nothrow_t&) throw();????????? // see [Item 49](http://blog.csdn.net/fatalerror99/archive/2006/02/28/612673.aspx)
```
如果你在一個 class 中聲明了任何 operator news,都將覆蓋所有這些標準形式。除非你有意防止 class 的客戶使用這些形式,否則,除了你創建的任何自定義 new 形式以外,還要確保它們都可以使用。當然,還要確保為每一個你使其可用的 operator new 提供相應的 operator delete。如果你要這些函數具有通常的行為,只需要讓你的 class-specific(類專用)版本去調用 global(全局)版本即可。
達到這種效果的一個簡單方法是創建一個包含 new 和 delete 的全部常規形式的 base class(基類):
```
class StandardNewDeleteForms {
public:
? **// normal new/delete**
? static void* operator new(std::size_t size) throw(std::bad_alloc)
? { return ::operator new(size); }
? static void operator delete(void *pMemory) throw()
? { ::operator delete(pMemory); }
? **// placement new/delete**
? static void* operator new(std::size_t size, void *ptr) throw()
? { return ::operator new(size, ptr); }
? static void operator delete(void *pMemory, void *ptr) throw()
? { return ::operator delete(pMemory, ptr); }
? **// nothrow new/delete**
? static void* operator new(std::size_t size, const std::nothrow_t& nt) throw()
? { return ::operator new(size, nt); }
? static void operator delete(void *pMemory, const std::nothrow_t&) throw()
? { ::operator delete(pMemory); }
};
```
想要在標準形式之外增加自定義形式的客戶就能夠使用 inheritance(繼承)和 using declarations(使用聲明)(參見 [Item 33](http://blog.csdn.net/fatalerror99/archive/2005/09/13/479864.aspx))來得到標準形式:
```
class Widget: public StandardNewDeleteForms {?????????? // inherit std forms
public:
?? using StandardNewDeleteForms::operator new;????????? // make those
?? using StandardNewDeleteForms::operator delete;?????? // forms visible
?? static void* operator new(std::size_t size,????????? // add a custom
???????????????????????????? std::ostream& logStream)?? // placement new
???? throw(std::bad_alloc);
?? static void operator delete(void *pMemory,?????????? // add the corres-
?????????????????????????????? std::ostream& logStream) // ponding place-
??? throw();??????????????????????????????????????????? // ment delete
? ...
};
```
**Things to Remember**
* 在編寫一個 operator new 的 placement 版本時,確保同時編寫 operator delete 的相應的 placement 版本。否則,你的程序可能會發生微妙的,斷續的 memory leaks(內存泄漏)。
* 當你聲明 new 和 delete 的 placement 版本時,確保不會無意中覆蓋這些函數的常規版本。
- Preface(前言)
- Introduction(導言)
- Terminology(術語)
- Item 1: 將 C++ 視為 federation of languages(語言聯合體)
- Item 2: 用 consts, enums 和 inlines 取代 #defines
- Item 3: 只要可能就用 const
- Item 4: 確保 objects(對象)在使用前被初始化
- Item 5: 了解 C++ 為你偷偷地加上和調用了什么函數
- Item 6: 如果你不想使用 compiler-generated functions(編譯器生成函數),就明確拒絕
- Item 7: 在 polymorphic base classes(多態基類)中將 destructors(析構函數)聲明為 virtual(虛擬)
- Item 8: 防止因為 exceptions(異常)而離開 destructors(析構函數)
- Item 9: 絕不要在 construction(構造)或 destruction(析構)期間調用 virtual functions(虛擬函數)
- Item 10: 讓 assignment operators(賦值運算符)返回一個 reference to *this(引向 *this 的引用)
- Item 11: 在 operator= 中處理 assignment to self(自賦值)
- Item 12: 拷貝一個對象的所有組成部分
- Item 13: 使用對象管理資源
- Item 14: 謹慎考慮資源管理類的拷貝行為
- Item 15: 在資源管理類中準備訪問裸資源(raw resources)
- Item 16: 使用相同形式的 new 和 delete
- Item 17: 在一個獨立的語句中將 new 出來的對象存入智能指針
- Item 18: 使接口易于正確使用,而難以錯誤使用
- Item 19: 視類設計為類型設計
- Item 20: 用 pass-by-reference-to-const(傳引用給 const)取代 pass-by-value(傳值)
- Item 21: 當你必須返回一個對象時不要試圖返回一個引用
- Item 22: 將數據成員聲明為 private
- Item 23: 用非成員非友元函數取代成員函數
- Item 24: 當類型轉換應該用于所有參數時,聲明為非成員函數
- Item 25: 考慮支持不拋異常的 swap
- Item 26: 只要有可能就推遲變量定義
- Item 27: 將強制轉型減到最少
- Item 28: 避免返回對象內部構件的“句柄”
- Item 29: 爭取異常安全(exception-safe)的代碼
- Item 30: 理解 inline 化的介入和排除
- Item 31: 最小化文件之間的編譯依賴
- Item 32: 確保 public inheritance 模擬 "is-a"
- Item 33: 避免覆蓋(hiding)“通過繼承得到的名字”
- Item 34: 區分 inheritance of interface(接口繼承)和 inheritance of implementation(實現繼承)
- Item 35: 考慮可選的 virtual functions(虛擬函數)的替代方法
- Item 36: 絕不要重定義一個 inherited non-virtual function(通過繼承得到的非虛擬函數)
- Item 37: 絕不要重定義一個函數的 inherited default parameter value(通過繼承得到的缺省參數值)
- Item 38: 通過 composition(復合)模擬 "has-a"(有一個)或 "is-implemented-in-terms-of"(是根據……實現的)
- Item 39: 謹慎使用 private inheritance(私有繼承)
- Item 40: 謹慎使用 multiple inheritance(多繼承)
- Item 41: 理解 implicit interfaces(隱式接口)和 compile-time polymorphism(編譯期多態)
- Item 42: 理解 typename 的兩個含義
- Item 43: 了解如何訪問 templatized base classes(模板化基類)中的名字
- Item 44: 從 templates(模板)中分離出 parameter-independent(參數無關)的代碼
- Item 45: 用 member function templates(成員函數模板) 接受 "all compatible types"(“所有兼容類型”)
- Item 46: 需要 type conversions(類型轉換)時在 templates(模板)內定義 non-member functions(非成員函數)
- Item 47: 為類型信息使用 traits classes(特征類)
- Item 48: 感受 template metaprogramming(模板元編程)
- Item 49: 了解 new-handler 的行為
- Item 50: 領會何時替換 new 和 delete 才有意義
- Item 51: 編寫 new 和 delete 時要遵守慣例
- Item 52: 如果編寫了 placement new,就要編寫 placement delete
- 附錄 A. 超越 Effective C++
- 附錄 B. 第二和第三版之間的 Item 映射