Item 51: Adhere to convention when writing new and delete.
Item 50介紹了如何自定義new
和delete
但沒有解釋你必須遵循的慣例, 這些慣例中有些並不直觀,所以你需要記住它們!
operator new
需要無限循環地獲取資源,如果沒能獲取則調用”new handler”,不存在”new handler”時應該拋出異常;operator new
應該處理size == 0
的情況;operator delete
應該兼容空指針;operator new/delete
作為成員函數應該處理size > sizeof(Base)
的情況(因為繼承的存在)。
Item 49指出了如何將operator new
重載為類的成員函數,在此我們先看看如何實現一個外部(非成員函數)的operator new
:operator new
應當有正確的返回值,在內存不足時應當調用”new handler”,請求申請大小為0的內存時也可以正常執行,避免隱藏全局的(”normal form”)new
。
bad_alloc
異常。考慮到上述目標,一個非成員函數的operator new
大致實現如下:
void * operator new(std::size_t size) throw(std::bad_alloc){
if(size == 0) size = 1;
while(true){
// 嘗試申請
void *p = malloc(size);
// 申請成功
if(p) return p;
// 申請失敗,獲得new handler
new_handler h = set_new_handler(0);
set_new_handler(h);
if(h) (*h)();
else throw bad_alloc();
}
}
size == 0
時申請大小為1
看起來不太合適,但它非常簡單而且能正常工作。況且你不會經常申請大小為0的空間吧?set_new_handler
調用先把全局”new handler”設置為空再設置回來,這是因為無法直接獲取”new handler”,多線程環境下這裡一定需要鎖。while(true)
意味著這可能是一個死循環。所以Item 49提到,”new handler”要麼釋放更多內存、要麼安裝一個新的”new handler”,如果你實現了一個無用的”new handler”這裡就是死循環了。
重載operator new
為成員函數通常是為了對某個特定的類進行動態內存管理的優化,而不是用來給它的子類用的。 因為在實現Base::operator new()
時,是基於對象大小為sizeof(Base)
來進行內存管理優化的。
當然,有些情況你寫的
Base::operator new
是通用於整個class及其子類的,這時這一條規則不適用。
class Base{
public:
static void* operator new(std::size_t size) throw(std::bad_alloc);
};
class Derived: public Base{...};
Derived *p = new Derived; // 調用了 Base::operator new !
子類繼承Base::operator new()
之後,因為當前對象不再是假設的大小,該方法不再適合管理當前對象的內存了。 可以在Base::operator new
中判斷參數size
,當大小不為sizeof(Base)
時調用全局的new
:
void *Base::operator new(std::size_t size) throw(std::bad_alloc){
if(size != sizeof(Base)) return ::operator new(size);
...
}
上面的代碼沒有檢查size == 0
!這是C++神奇的地方,大小為0的獨立對象會被插入一個char
(見Item 39)。 所以sizeof(Base)
永遠不會是0,所以size == 0
的情況交給::operator new(size)
去處理了。
這裡提一下operator new[]
,它和operator new
具有同樣的參數和返回值, 要注意的是你不要假設其中有幾個對象,以及每個對象的大小是多少,所以不要操作這些還不存在的對象。因為:
size
不一定等於sizeof(Base)
。size
實參的值可能大於這些對象的大小之和。因為Item 16中提到,數組的大小可能也需要存儲。
相比於new
,實現delete
的規則要簡單很多。唯一需要注意的是C++保證了delete
一個NULL
總是安全的,你尊重該慣例即可。
同樣地,先實現一個外部(非成員)的delete
:
void operator delete(void *rawMem) throw(){
if(rawMem == 0) return;
// 釋放內存
}
成員函數的delete也很簡單,但要注意如果你的new
轉發了其他size
的申請,那麼delete
也應該轉發其他size
的申請。
class Base{
public:
static void * operator new(std::size_t size) throw(std::bad_alloc);
static void operator delete(void *rawMem, std::size_t size) throw();
};
void Base::operator delete(void *rawMem, std::size_t size) throw(){
if(rawMem == 0) return; // 檢查空指針
if(size != sizeof(Base)){
::operator delete(rawMem);
}
// 釋放內存
}
注意上面的檢查的是
rawMem
為空,size
是不會為空的。
其實size
實參的值是通過調用者的類型來推導的(如果沒有虛析構函數的話):
Base *p = new Derived; // 假設Base::~Base不是虛函數
delete p; // 傳入`delete(void *rawMem, std::size_t size)`的`size == sizeof(Base)`。
如果Base::~Base()
聲明為virtual
,則上述size
就是正確的sizeof(Derived)
。 這也是為什麼Item 7指出析構函數一定要聲明virtual
。