# Item 51: 編寫 new 和 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/)
[Item 50](http://blog.csdn.net/fatalerror99/archive/2006/12/26/1463166.aspx) 講解了什么時候你可能需要編寫 operator new 和 operator delete 的你自己的版本,但是沒有講解當你這樣做時必須遵循的慣例。這些規則并不難以遵循,但有一些不那么直觀,所以了解它們是什么非常重要。
我們從 operator new 開始。實現一個符合慣例的 operator new 需要有正確的返回值,在沒有足夠的內存可用時調用 new-handling function(參見 [Item 49](http://blog.csdn.net/fatalerror99/archive/2006/02/28/612673.aspx)),并做好應付無內存請求的準備。你還要避免無意中對 new 的“常規”形式的覆蓋,雖然這更多的是一個 class interface(類接口)的問題,而并非是一個實現的需求,它將在 [Item 52](http://blog.csdn.net/fatalerror99/archive/2007/01/21/1489466.aspx) 討論。
operator new 的返回值部分很容易。如果你能提供所請求的內存,你就返回一個指向它的指針。如果你不能,你應該遵循 [Item 49](http://blog.csdn.net/fatalerror99/archive/2006/02/28/612673.aspx) 描述的規則并拋出一個 bad_alloc 類型的 exception(異常)。
然而,它也不完全那么簡單,因為 operator new 實際上不止一次設法分配內存,每次失敗后調用 new-handling function。在此假設 new-handling function 能做些事情釋放一些內存。只有當指向 new-handling function 的指針為空時,operator new 才拋出一個 exception(異常)。
奇怪的是,C++ 要求即使請求零字節,operator new 也要返回一個合理的指針。(需要這種怪異的行為來簡化語言的其它部分。)在這種情況下,一個 non-member(非成員)的 operator new 的偽代碼如下:
```
void * operator new(std::size_t size) throw(std::bad_alloc)
{????????????????????????????????????? // your operator new might
? using namespace std;???????????????? // take additional params
? if (size == 0) {???????????????????? // handle 0-byte requests
??? size = 1;????????????????????????? // by treating them as
? }??????????????????????????????????? // 1-byte requests
? while (true) {
?? _attempt to allocate size bytes;_
??? if (_the allocation was successful_)
?????? return (_a pointer to the memory_);
??? // allocation was unsuccessful; find out what the
??? // current new-handling function is (see below)
??? new_handler globalHandler = set_new_handler(0);
??? set_new_handler(globalHandler);
??? if (globalHandler) (*globalHandler)();
??? else throw std::bad_alloc();
? }
}
```
將零字節請求當作他們真的請求了一個字節來處理的竅門看起來很齷齪,但是它簡單,合法,有效,無論如何,你估摸著的請求零字節這種事情發生的頻率有多大呢?
你可能在不經意中還看到偽代碼中將 new-handling function pointer 設置為空,然后又馬上重置為它原來的值。遺憾的是,沒有辦法直接得到 new-handling function pointer,所以你必須調用 set_new_handler 以得知它是什么。拙劣,的確,但是也有效,至少對 single-threaded(單線程)代碼沒問題。在 multithreaded(多線程)環境中,你可能需要某種鎖以便安全地擺布隱藏在 new-handling function 后面的(全局的)data structures(數據結構)。
[Item 49](http://blog.csdn.net/fatalerror99/archive/2006/02/28/612673.aspx) 談及 operator new 包含一個無限循環,而上面的代碼明確地展示了這個循環,"while (true)" 差不多會盡其所能地無限做下去。跳出循環的唯一出路是內存被成功分配或 new-handling function 做了 [Item 49](http://blog.csdn.net/fatalerror99/archive/2006/02/28/612673.aspx) 中描述的事情之一:使得更多的內存可用,安裝一個不同的 new-handler,卸載 new-handler,拋出一個 bad_alloc 或從 bad_alloc 派生的 exception(異常),或不再返回。現在,new-handler 為什么要做這些事情之一已經很清楚了。如果它不這樣做,operator new 內的循環永遠不會停止。
很多人沒有意識到 operator new member functions(成員函數)會被 derived classes(派生類)繼承。這會引起一些有趣的復雜性。在前面的 operator new 偽代碼中,注意那個函數設法分配 size 個字節(除非 size 是零)。因為它是傳遞給這個函數的 argument(實參),所以它有著明確的意義。然而,就像 [Item 50](http://blog.csdn.net/fatalerror99/archive/2006/12/26/1463166.aspx) 所講的,編寫一個自定義的內存管理器的最常見的原因之一是為了優化某個特定 class 的 objects 的分配,而不是某個 class 或它的任何 derived classes(派生類)的。也就是說,給定一個 class X 的 operator new,這個函數的行為通常是為大小為 sizeof(X) 的 objects 調諧的——絕不會更大或者更小。然而,由于 inheritance(繼承),就有可能一個 base class(基類)中的 operator new 被調用來為一個 derived class(派生類)的 object 分配內存:
```
class Base {
public:
? **static void * operator new(std::size_t size) throw(std::bad_alloc)**;
? ...
};
class Derived: public Base??????????????? // Derived doesn't declare
{ ... };????????????????????????????????? // operator new
Derived *p = **new Derived**;???????????????? // calls Base::operator new!
```
如果 Base 的 class-specific(類專用)的 operator new 不是被設計成應付這種情況的——它很可能不是——它處理這種局面的最佳辦法就是把這個請求“錯誤”內存量的調用甩給 standard operator new,就像這樣:
```
void * Base::operator new(std::size_t size) throw(std::bad_alloc)
{
? **if (size != sizeof(Base))**?????????????? // if size is "wrong,"
???? **return ::operator new(size)**;???????? // have standard operator
????????????????????????????????????????? // new handle the request
? ...???????????????????????????????????? // otherwise handle
????????????????????????????????????????? // the request here
}
```
“不許動!”我聽到你喊,“你忘了檢查 size 是零這種 pathological-but-nevertheless-possible(病態然而可能)的情況!”實際上,我沒有,還有,當你大聲抱怨的時候拜托不要使用連字符。測試依然在那,它只是與 size 和 sizeof(Base) 的比較合在了一起。C++ 工作在一些神秘的方式中,這些方式之一就是強制規定所有的獨立 objects 都具有非零的大小(參見 [Item 39](http://blog.csdn.net/fatalerror99/archive/2005/11/22/535067.aspx))。根據定義,sizeof(Base) 絕不會是零,所以如果 size 是零,請求將轉發給 ::operator new,而以一種合理的方式處置這個請求就成為那個函數的職責。
如果你想要在每一個 class 的基礎上控制數組的內存分配,你需要實現 operator new 的專用于數組的兄弟,operator new[]。(這個 function 通常被叫做 "array new",因為要確定 "operator new[]" 如何發音實在是太難了。)如果你決定要編寫 operator new[],記住你所做的全部是分配一大塊 raw memory(裸內存)——你不能針對還不存在的數組中的 objects 做任何事情。實際上,你甚至不能確定數組中會有多少個 objects。首先,你不知道每個 object 有多大。畢竟,一個 base class(基類)的 operator new[] 通過繼承可以被調用來為一個 derived class objects(派生類對象)的數組分配內存,而 derived class objects(派生類對象)通常都比 base class objects(基類對象)更大。
因此,在 Base::operator new[] 中,你不能斷定每一個加到數組中的 object 的大小一定是 sizeof(Base),而這就意味著,你不能斷定數組中的 objects 的數量是 (_bytes requested_)/sizeof(Base)。第二,傳遞給 operator new[] 的 size_t 參數可能比充滿 objects 的內存還要大一些,因為,就像 [Item 16](http://blog.csdn.net/fatalerror99/archive/2005/07/20/430119.aspx) 講到的,dynamically allocated arrays(動態分配數組)可能包括額外的空間用于存儲數組元素的數量。
編寫 operator new 時,你需要遵循的慣例也就到此為止了。對于 operator delete,事情就更簡單了,你需要記住的全部大約就是 C++ 保證刪除空指針總是安全的,所以你需要遵循這個保證。下面是一個非成員的 operator delete 的偽代碼:
```
void operator delete(void *rawMemory) throw()
{
? if (rawMemory == 0) return;??????????? // do nothing if the null
???????????????????????????????????????? // pointer is being deleted
? _deallocate the memory pointed to by rawMemory;_
}
```
這個函數的成員版本也很簡單,只是你必須確保檢查被刪除東西的大小。假設你的 class-specific(類專用)的 operator new 將“錯誤”大小的請求轉發給 ::operator new,你也可以將“錯誤大小”的刪除請求轉發給 ::operator delete:
```
class Base {??????????????????????????? // same as before, but now
public:???????????????????????????????? // operator delete is declared
? static void * operator new(std::size_t size) throw(std::bad_alloc);
? **static void operator delete(void *rawMemory, std::size_t size) throw();**
? ...
};
void Base::operator delete(void *rawMemory, std::size_t size) throw()
{
? if (rawMemory == 0) return;?????????? // check for null pointer
? **if (size != sizeof(Base)) {**?????????? // if size is "wrong,"
???? **::operator delete(rawMemory);**????? // have standard operator
???? **return;**??????????????????????????? // delete handle the request
? **}**
? _deallocate the memory pointed to by rawMemory;_
? return;
}
```
有趣的是,如果被刪除的 object 是從一個缺少 virtual destructor(虛擬析構函數)的 base class(基類)派生出來的,C++ 傳遞給 operator delete 的 size_t 值也許是不正確的。這已經足夠作為“確保你的 base classes(基類)擁有 virtual destructors(虛擬析構函數)”的原因了,除此之外,[Item 7](http://blog.csdn.net/fatalerror99/archive/2005/07/10/419235.aspx) 描述了另一個,論證得更好的原因。至于當前,簡單地記住如果你在 base classes(基類)中遺漏了 virtual destructors(虛擬析構函數),operator delete functions 可能無法正確工作。
**Things to Remember**
* operator new 應該包含一個設法分配內存的無限循環,如果它不能滿足一個內存請求,應該調用 new-handler,還應該處理零字節請求。class-specific(類專用)版本應該處理對比預期更大的區塊的請求。
* operator delete 如果收到一個空指針應該什么都不做。class-specific(類專用)版本應該處理比預期更大的區塊。?
- 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 映射