C++進階_Effective_C++第三版(八) 定製new和delete Customizing new and delete

定製new和delete

Customizing new and delete
多線程環境下的內存管理,比單線程系統更加複製,由於heap是一個可被改動的全局性資源,因此多線程系統訪問這一類資源的競速狀態出現機會。使用無鎖算法或精心防止併發訪問時,調用內存可能很容易導致管理heap的數據結構內容出錯。

49、瞭解new-handler的行爲

Understand the behavior of the new-handler.
當operator new無法滿足某一內存分配需求時,它會派出異常,以前會返回一個null指針。新式的編譯器支持,在未獲滿足的內存需求之前,先調用一個客戶指定的錯誤處理函數,一個所謂的new-handler。爲了指定這個“用以處理內存不足”的函數,客戶必須調用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聲明式尾端“throw()”是一份異常明細,表示該函數不拋出任何異常。set_new_handler的參數是個指針,指向operator new無法分配足夠內存時候被調用的函數。其返回值也是個指針,指向set_new_handler被調用前正在執行的那個new_handler函數。可以如下使用:

void outOfMem()
{
	std::cerr<<”Unable to satisfy request for memory\n”;
	std::abort();
}
int main()
{
	std::set_new_handler(outOfMem);
	int* pBigDataArray = new int[100000000L];}

上述程序申請空間失敗後調用outOfMem,打印輸出後結束程序。
new_handler函數設計的幾點要求:讓更多的內存可被使用,安裝另一個new_handler,卸除new_handler(將null指針傳給set_new_handler)。不返回,通常調用abort或exit。
C++的operator new在舊版本無法分配足夠內存時返回null,新版本的是拋出bad_alloc異常,考慮到兼容性問題,提供另一種機制實現分配失敗返回null。被稱爲nothrow形式:

class Widget{		};
Widget* pw1 = new Widget; 			//分配失敗拋出bad_alloc
if(pw1 == 0)//這個測試一定失敗
Widget* pw2 = new (std::nothrow) Widget; //如果分配失敗,返回0
if(pw2 == 0)//這個測試可能成功

nothrow new對異常的強制保證性並不高,因爲在表達式中發生兩件事,第一nothrow版的operator new被調用,用以分配足夠內存給Widget對象。如果分配失敗便返回null指針,第二如果分配成功,Widget構造函數會被調用,此時Widget構造函數內如果有new一些內存,無法強迫它再次使用nothrow new。

  • set_new_handler允許客戶指定一個函數,在內存分配無法獲得滿足時被調用。
  • Nothrow new是一個頗爲侷限的工具,因爲它只適用於內存分配,後繼的構造函數調用還是可能拋出異常。

50、瞭解new和delete的合理替換時機

Understand when it makes sense to replace new and delete.
寫一個定製型operator new和operator delete如下:

static const int signature = 0xDEADBEEF;
typedef unsigned char Byte;
void* operator new(std::size_t size) throw(std::bad_alloc)
{
	using namespace std;
	size_t realSize = size+2*sizeof(int);//增加大小,使能夠塞入兩個signature
	void *pMem = malloc(realSize);
	if(!pMem) throw bad_alloc();
	//將signature寫入內存的最前段落和最後段落
	*( static_cast< int *>(pMem)) = signature;
	*(reinterpret_cast<int *>( static_cast< Byte *>(pMem)+realSize-sizeof(int))) = signature;
	return static_cast< Byte *>(pMem) + sizeof(int);
}

替換編譯器提供的operator new和operator delete理由:
用來檢測運用上的錯誤:如果將new所得內存delete掉卻不幸失敗,會導致內存泄漏,如果new所得內存身上多次delete則會導致不確定行爲等。
爲了強化效能:編譯器版本所帶的operator new和operator delete主要用於一般目的,不但可被長時間執行的程序接受,也可被執行時間少於一秒的程序接受,而且還包括大塊內存,小塊內存,大小混合型內存等等,編譯器版本採取的是中庸之道,對於每個需求都是適度的好。
爲了收集使用上的統計數據:在使用分配內存之前,應該先收集軟件如何使用其動態內存。分配區塊的大小如何分佈,使用壽命,哪種次序來分配和歸還等。
爲了增加分配和歸還的速度:泛用性分配器往往比定製型分配器慢,特別是當定製型分配器專門針對某特定類型的對象而設計時。
爲了降低缺省內存管理器帶來的空間額外開銷:泛用性內存管理器往往不只比定製型慢,它們往往還使用更多內存,因爲它們常常在每個分配區身上加一些額外開銷。
爲了彌補缺省分配器中的非最佳對齊:在x86體系結構上都是8-byte對齊的化doubles的訪問最是快速。
爲了將相關對象成簇集中:如果知道特定的某個數據結構往往被一起使用,且希望處理這些數據時將內存頁錯誤的頻率降至最低。這樣可以將它們成簇集中在儘可能少的內存頁上。
爲了獲得非傳統的行爲:如可能會希望分配和歸還共享內存內的區塊。

  • 有許多理由需要寫個自定的new和delete,包括改善效能、對heap運用錯誤進行調試、收集heap使用信息。

51、編寫new和delete時需固守常規

Adhere to convention when writing new and delete.
對於定製型operator new和operator delete編寫要遵守一些規則,最好的實現方式就是保持和原始的new和delete保持一致。
比如對於new,應該提供如果內存不足時必須調用new-handling函數,必須對零內存需求處理:

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 throw(std::bad_alloc);
	}
}
void operator delete(void *rawMemory) throw()
{
	if(rawMemory == 0) return;
	//歸還rawMemory所指的內存
}
  • operator
    new應該內含一個無窮循環,並在其中嘗試分配內存,如果它無法滿足內存需求,就該調用new-handler。它也應該有能力處理0bytes申請。Class專屬版本則還應該處理比正確大小更大的錯誤申請。
  • operator delete應該在收到null指針時不做任何事。Class專屬版本則還應該處理比正確大小更大的錯誤申請。

上一篇: C++進階_Effective_C++第三版(七) 模板與泛型編程 Templates and Generic Programming
下一篇: C++進階_Effective_C++第三版(一) 讓自己習慣C++ Accustoming Yourself to C++

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章