Effective C++筆記 —— 第八章

  1. 當operator new拋出異常以反映一個未滿足的內存需求之前,它會先調用一個客戶指定的錯誤處理函數,一個所謂的set-new-handler
    namespace std{
     typedef void(*new_handler)();
     new_handler set_new_handler(new_handler p) throw();
    }
    
    new_handler是個typedef,定義出一個指針指向函數,該函數沒有參數也沒有返回值。set_new_handler則是“獲得一個new_handler並返回一個new_handler”的函數。
    可以這樣用set_new_handler:
    void outOfMem(){
    	std::cerr<<"unable to satisfy request for memory";
    	std::abort();
    }
    int main()
    {
    	std::set_new_handler(outOfMem);
    	int* pBigDataArray=new int[1000000000000];
    }
    
    如果想支持class專屬之new_handler,可以令每一個class提供自己的set_new_handler和operator new即可,其中set_new_handler使客戶得以指定class的專屬new_handler,至於operator new則確保在分配class對象內存的過程中以class專屬之new_handler替換global new_handler
  2. 齊位意義重大,因爲C++要求所有operator new返回的指針都有適當的對齊。malloc就是在這樣的要求下工作,所以令operator new返回一個得自malloc的指針是安全的。如果返回一個得自malloc且偏移一個int大小的指針,沒人能保證它的安全
  3.  	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();
     		}
     	}
    
    我們沒有辦法直接取得new_handler函數指針,所以必須調用set_new_handler找出它來
    while是個無窮循環,退出循環的唯一辦法是:內存被分配成功或new_handlering函數做了一件事情:讓更多內存可用、安裝另一個new_handler、卸除new_handler、拋出bad_alloc異常(或其派生物)、或是承認失敗而直接return。現在,對於new_handler爲什麼必須做出上述其中某些事情應該很清楚了,因爲不那麼做operator new內的while循環永遠不會結束
    如果發生了繼承,會發生分配大小錯誤
    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
    
    處理此情勢的最佳做法是將“內存申請量錯誤”的調用行爲改採用標準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);
    		...
    	}
    
  4. 重寫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所指的內存
       		...
       	}
       ```
    
  5. 寫了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
    delete pw;     //調用正常的operator delete
    
    如果對所有與placement new相關的內存泄漏宣戰,我們必須同時提供一個正常的operator delete(用於構造期間無任何異常被拋出)和一個placement版本(用於構造期間有異常被拋出)
  6. 由於成員函數的名稱會掩蓋其外圍作用域中的相同名稱,即使參數列表不同,所以需要小心避免讓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版本
    
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章