- 當operator new拋出異常以反映一個未滿足的內存需求之前,它會先調用一個客戶指定的錯誤處理函數,一個所謂的set-new-handler
new_handler是個typedef,定義出一個指針指向函數,該函數沒有參數也沒有返回值。set_new_handler則是“獲得一個new_handler並返回一個new_handler”的函數。namespace std{ typedef void(*new_handler)(); new_handler set_new_handler(new_handler p) throw(); }
可以這樣用set_new_handler:
如果想支持class專屬之new_handler,可以令每一個class提供自己的set_new_handler和operator new即可,其中set_new_handler使客戶得以指定class的專屬new_handler,至於operator new則確保在分配class對象內存的過程中以class專屬之new_handler替換global new_handlervoid outOfMem(){ std::cerr<<"unable to satisfy request for memory"; std::abort(); } int main() { std::set_new_handler(outOfMem); int* pBigDataArray=new int[1000000000000]; }
- 齊位意義重大,因爲C++要求所有operator new返回的指針都有適當的對齊。malloc就是在這樣的要求下工作,所以令operator new返回一個得自malloc的指針是安全的。如果返回一個得自malloc且偏移一個int大小的指針,沒人能保證它的安全
-
我們沒有辦法直接取得new_handler函數指針,所以必須調用set_new_handler找出它來void* operator new(std::size_t size) throw(std::bad_alloc) { using namespace std; if(size==0) size=1; while(true) { 嘗試分配size bytes if(分配成功) return (一個指針,指向分配得來的內存); //分配失敗;找出目前的new-handling函數 new_handler globalHandler=set_new_handler(0); set_new_handler(globalHandler); if(globalHandler) (*globalHandler)(); else throw std::bad_alloc(); } }
while是個無窮循環,退出循環的唯一辦法是:內存被分配成功或new_handlering函數做了一件事情:讓更多內存可用、安裝另一個new_handler、卸除new_handler、拋出bad_alloc異常(或其派生物)、或是承認失敗而直接return。現在,對於new_handler爲什麼必須做出上述其中某些事情應該很清楚了,因爲不那麼做operator new內的while循環永遠不會結束
如果發生了繼承,會發生分配大小錯誤
處理此情勢的最佳做法是將“內存申請量錯誤”的調用行爲改採用標準operator new,像這樣:class Base{ static void* operator new(std::size_t size) throw(std::bad_alloc); ... } class Derived:public Base {...}; Derived* p=new Derived; //這裏調用的是Base::operator new
void* Base::operator new(std::size_t size) throw(std::bad_alloc) { using namespace std; if(size!==sizeof(Base)) return ::operator new(size); ... }
- 重寫operator delete需要記住的唯一事情就是C++保證“刪除null指針永遠安全”,所以應該這樣寫:
void operator delete(void* rawMemory) throw() { if(rawMemory==0) return; 現在,歸還rawMemory所指的內存 } ``` ```javascript void* Base::operator delete(void* rawMemory,std::size_t size) throw() { using namespace std; if(rawMemory==0) return; if(size!==sizeof(Base)) return ::operator delete(rawMemory); 現在,歸還rawMemory所指的內存 ... } ```
- 寫了placement new也要寫placement delete
如果operator new接受的參數除了一定會有的那個size_t之外還有其他,這便是個所謂的placement new。如果一個帶額外參數的operator new沒有“帶相同額外參數”的對應版本的operator delete,那麼當new的內存分配動作需要取消並恢復舊觀時就沒有任何operator delete會被調用,會發生內存泄漏(分配內存時的operator new調用成功,類類型的構造函數拋出異常的情況下)
placement delete只有在“伴隨placement new調用而觸發的構造函數”出現異常時纔會被調用。對着一個指針施行delete絕不會導致調用placement delete
如果對所有與placement new相關的內存泄漏宣戰,我們必須同時提供一個正常的operator delete(用於構造期間無任何異常被拋出)和一個placement版本(用於構造期間有異常被拋出)delete pw; //調用正常的operator delete
- 由於成員函數的名稱會掩蓋其外圍作用域中的相同名稱,即使參數列表不同,所以需要小心避免讓class專屬的news掩蓋客戶期望的其他news(包括正常版本)。假設一個base class,其中聲明唯一一個placement operator new,客戶端會發現他們無法使用正常形式的new:
class Base{ public: ... static void* operator new(std::size_t size,std::ostream& logStream) throw(std::bad_alloc); //這個new會掩蓋正常的形式 ... }; Base* pb=new Base; //錯誤,因爲正常形式的operator new被掩蓋 Base* pb=new (std::cerr) Base; //正確,調用Base的placement new ``` 同樣,derived classes中的operator new會掩蓋global版本和繼承而來的operator new版本
Effective C++筆記 —— 第八章
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.