標籤:new delete 高效c++ c++
條款 50已經說明為什麼要寫自己的operator new和operator delete,本條款解釋在編寫時遵循什麼規則。
從operator new開始。operator new必須返回正確的值,記憶體不足時必須調用new-handling函數,要有對付零記憶體需求的準備,避免不慎掩蓋正常形式的new–這比較偏近class介面的要求而非實現要求。正常形式的new描述與條款 52。
operator new如果申請記憶體成功,就返回指向那塊記憶體的指標,失敗則遵循條款 49描述,拋出bad_alloc異常。它實際上不知一次嘗試記憶體配置,在每次失敗後都調用new-handling函數。這裡假設new-handling函數能做某些動作將一些記憶體釋放出來。只有指向new-hangling函數的指標為,才會拋出異常。C++規定,即使客戶要求0 byte,operator new也要返回一個合法指標。下面是個non-member operator new的偽碼(pseudocode):
void* operator new(std::size_t size) throw(std::bad_alloc){ using namespace std; if(size==0){//處理0-byte申請 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(); }}
如果在多線程環境下,還需要某種鎖機制,以便處理new-handling函數背後的global資料結構。
上麵包含一個死迴圈,退出此死迴圈唯一辦法就是記憶體配置成功(假設new-handling不為null),所以new-handling函數做的事是:讓更多記憶體可用、安裝另一個new-handler、卸載new-handler、拋出bad_alloc異常(或其衍生類別),或承認失敗直接return。
上面的operator new成員函數可能會被derived classes繼承。注意分配記憶體大小size,它是函數接收的實參。**條款**50提到,定製記憶體 Clerk往往是為了特定的class對象,以此來最佳化,不是為了該class的derived classes。
class Base{public: static void* operator new(std::size_t size) throw(std::bad_alloc) ……};class Derived:public Base{……};//假設為重新定義operator newDerived* p=new Derived;//這裡調用了Base::operator new如果是class專屬的operator new,應該改為這樣:void* Base::operator new(std::size_t size) throw(std::bad_alloc){ if(size!=sizeof(Base)) return ::operator new(size);//使用標準的operator new ……}
如果你打算控制class專屬版的arrays記憶體配置,那麼需要實現operator new[]。編寫operator new[]時,唯一要做的一件事就是分配一塊未加工的記憶體(raw memory),因為你無法對array之內迄今尚未存在的元素對象做任何事。甚至我們無法知道這個array含有多少個元素對象。可能你不知道每個對象多大,因為base class的operator new[]有可能經由繼承被調用,將記憶體配置給derived class對象的array使用。
所以不能再Base::operator new[]中假設每個元素對象大小是sizeof(Base),這樣就是說你不能假設array元素個數是(bytes申請數/sizeof(Base))。此外,傳遞給operator new[]的size_t參數,其值有可能比將輩填對象的記憶體大一些,因為條款 16提過,動態分配的arrays可能包含額外空間用來存放元素個數。
operator delete情況就簡單很多,但是要記住,C++保證刪除指標永遠安全。下面是non-member operator delete的偽碼(pseudocode):
void operator delete(void* rawMemory) throw(){ if(rawMemory==0) return; 歸還rawMemory所指記憶體;}
這個函數的member版本也很簡單,只需多加一個檢查刪除數量。
void Base::operator delete(void rawMemory, std::size_t size) throw(){ if(rawMemory==0) return; if(size!=sizeof(Base)){ ::operator delete(rawMemory); return ; } 歸還rawMemory所指記憶體; return ;}
如果即將刪除的對象派生自某個base class而後者沒有virtual解構函式,那麼C++傳給operator delete的size_t數值可能不正確。
總結
- operator new應該內涵死迴圈,並在其中嘗試分配記憶體,如果它無法滿足記憶體需求,就該調用new-handler。它也應該有能力處理0bytes申請。class專屬版本的還應該處理“比正確大小更大的(錯誤)申請”。
- operator delete應該在收到指標時不做任何事。class專屬版本則還應該處理“比正確大小更大的(錯誤)申請”。
《Effective C++》:條款51:編寫new和delete時需固守常規