一.new操作符的概念
我們通常講的new是指的是new operator,其實還有另外兩個概念,operator new 和 placement new。
1、new operator
我們在使用new operator的時候,實際上是執行了三個步驟:
1)調用operator new分配內存 ;2)調用構造函數生成類對象;3)返回相應指針。
2、operator new
所以說operator new做的事情是new operator的一部分。
operator new的原型是
Void* operator new(size_t size);
參數size指定待分配的內存大小,函數內部調用malloc初始化內存,返回指向這個內存的指針。
你可以重載這個函數(注意是重載operator new,而不能重載new operator)。operator new默認情況下首先調用分配內存的代碼,嘗試得到一段堆上的空間,如果成功就返回,如果失敗,則轉而去調用一個new_hander(異常處理函數),若沒有定義new_hander,則拋出異常,否則執行new_hander,然后繼續重復前面過程。你可以在重載的時候加上額外的參數,但是第一個參數類型必須是size_t.例如:
class A { public: void* operator new(size_t size) { printf("operator new calledn"); return ::operator new(size); } }; A* a = new A();
這里通過::operator new調用了原有的全局的new,實現了在分配內存之前輸出一句話。全局的operator new也是可以重載的,但這樣一來就不能再遞歸的使用new來分配內存,而只能使用malloc了:
void* operator new(size_t size) { printf("global newn"); return malloc(size); }
相應的,delete也有delete operator和operator delete之分,后者也是可以重載的。並且,如果重載了operator new,就應該也相應的重載operator delete,這是良好的編程習慣。
3、placement new
placement new是用來實現定位構造的,因此可以實現new operator三步操作中的第二步。
其實它也只是operator new的一個重載的版本,只是我們很少用到它。如果你想在已經分配的內存中創建一個對象,使用new時行不通的。也就是說placement new允許你在一個已經分配好的內存中(棧或者堆中)構造一個新的對象。原型中void*p實際上就是指向一個已經分配好的內存緩沖區的的首地址。
我 們知道使用new操作符分配內存需要在堆中查找足夠大的剩余空間,這個操作速度是很慢的,而且有可能出現無法分配內存的異常(空間不夠)。 placement new就可以解決這個問題。我們構造對象都是在一個預先准備好了的內存緩沖區中進行,不需要查找內存,內存分配的時間是常數;而且不會出現在程序運行中途 出現內存不足的異常。所以,placement new非常適合那些對時間要求比較高,長時間運行不希望被打斷的應用程序。
使用方法如下:
1. 緩沖區提前分配
可以使用堆的空間,也可以使用棧的空間,所以分配方式有如下兩種:
class MyClass {…}; char *buf=new char[N*sizeof(MyClass)+sizeof(int)];或者char buf[N*sizeof(MyClass)+sizeof(int)];
2. 對象的構造
MyClass * pClass=new(buf) MyClass;
3. 對象的銷毀
一旦這個對象使用完畢,你必須顯式的調用類的析構函數進行銷毀對象。但此時內存空間不會被釋放,以便其他的對象的構造。
pClass->~MyClass();
4. 內存的釋放
如果緩沖區在堆中,那么調用delete[] buf;進行內存的釋放;如果在棧中,那么在其作用域內有效,跳出作用域,內存自動釋放。
注意:
- 在C++標准中,對於placement operator new []有如下的說明: placement operator new[] needs implementation-defined amount of additional storage to save a size of array. 所以我們必須申請比原始對象大小多出sizeof(int)個字節來存放對象的個數,或者說數組的大小。
- 使用方法第二步中的new才是placement new,其實是沒有申請內存的,只是調用了構造函數,返回一個指向已經分配好的內存的一個指針,所以對象銷毀的時候不需要調用delete釋放空間,但必須調用析構函數銷毀對象。
placement new 是重載operator new 的一個標准、全局的版本,它不能夠被自定義的版本代替(不像普通版本的operator new 和 operator delete能夠被替換)。
void *operator new( size_t, void *p ) throw() { return p; }
placement new的執行忽略了size_t參數,只使用第二個參數。其結果是允許用戶把一個對象放到一個特定的地方,達到調用構造函數的效果。
和其他普通的new不同的是,它在括號里多了另外一個參數。比如:
Widget * p = new Widget; - - - - - - - - - //ordinary new
pi = new (ptr) int; pi = new (ptr) int; //placement new
括號里的參數ptr是一個指針,它指向一個內存緩沖器,placement new將在這個緩沖器上分配一個對象。Placement new的返回值是這個被構造對象的地址(比如括號中的傳遞參數)。placement new主要適用於:在對時間要求非常高的應用程序中,因為這些程序分配的時間是確定的;長時間運行而不被打斷的程序;
三、處理內存分配異常
正如前面所說,operator new的默認行為是請求分配內存,如果成功就返回,如果失敗,則轉而去調用一個new_hander(異常處理函數),若沒有定義new_hander,則拋出異常,否則執行new_hander,然后繼續重復前面過程。於是,想要從operator new的執行過程中返回,則必然需要滿足下列條件之一:
1)分配內存成功
2)new_handler中拋出bad_alloc異常
3)new_handler中調用exit()或類似的函數,使程序結束
於是,我們可以假設默認情況下operator new的行為是這樣的:
void* operator new(size_t size) { void* p = null while(!(p = malloc(size))) { if(null == new_handler) throw bad_alloc(); try { new_handler(); } catch(bad_alloc e) { throw e; } catch(…) {} } return p; }
當operator new不能滿足一個內存分配請求的時候,默認會拋出一個異常,我們可以通過設置new_handler定義處置策略。
new_handler的模型為:void (*new_handler)()
可以通過“void set_new_handler( void(*new_handler)()) throw();”設置這個處理函數(new_handler),它定義在<new>標准函數庫中:
namespace std { void (*new_handler)(); void set_new_handler( new_handler )throw(); }
//error-handling function void MemErrorHandling() { std::cerr << "Failed to allocate memory.\n"; std::abort(); } ... ... std::set_new_handler(MemErrorHandling);
現在我們知道了new操作失敗后,系統地大概處理流程,以及怎么設置用戶自定義處理函數,但是我們究竟可以在new_handler中做些什么處理呢?
1、刪除其它無用的內存,使系統具有可以更多的內存可以使用,為下一步的內存申請作准備。
2、設置另外一個new_handler。如果當前的new_handler不能夠做到更多的內存申請操作,或者它知道另外一個new_handler可 以做到,則可以調用set_new_handler函數設置另外一個new_handler,這樣在operator new下一次調用的時候,可以使用這個新的new_handler。
3、卸載new_handler(通過set_new_handler(0)),使operator new在下一次調用的時候,因為new_handler為空拋出內存申請異常。
4、拋出自定義異常。
5、不再返回,調用abort或者exit退出程序。
參考:
1、http://www.bc-cn.net/Article/kfyy/cjj/jszl/200604/4002.html
2、http://blog.csdn.net/youdianmengxiangba/article/details/8233651