7.預先準備好內存不夠的情況。
new在無法完成內存分配請求時,會拋出異常,異常了要怎么辦,這是一個很現實且以后絕對要碰到的問題。
在c中一般使用宏來分配內存并檢測分配是否成功,c++中類似以下函數:
~~~
#define NEW(PTR,TYPE) \
try { (PTR) = new TYPE;} \
catch (std::bad_alloc& ){assert(0);}
~~~
catch 的std::bad_alloc為new 操作符中 不能滿足內存分配請求時拋出的異常類型。
assert是在<assert.h>中宏,或者帶命名空間的<cassert> 。宏檢測傳給它的表達式的值是否為0,如果是0,就會發出一條出錯信息,并調用abort。assert在沒有定義標準宏NDEBUG時,即只在調試狀態中這么做,當產品發布后,即定義了NDEBUG后,assert什么也不做。
但使用宏是不夠的,因為其只能進行簡單的一個基本數據類型的內存分配,而對于數組以及有構造函數的數據類型無法使用。可以考慮重載operator new 。
而常用的簡單的出錯處理方法是,通過設置一個出錯處理函數,使內存分配請求不能滿足時,調用這個處理函數。因為operator new 操作符在不能滿足請求時,會在拋出異常前調用客戶指定的一個出錯處理函數,這個函數稱為new-handler函數。
指定出錯處理函數要用到set_new_handler 函數,該函數位于頭文件<new>中,其定義如下:
~~~
new_handler __cdecl set_new_handler(_In_opt_ new_handler)
_THROW0();
~~~
而new_handler的定義如下:
~~~
typedef void (__cdecl * new_handler) ();
~~~
new_handler為指向一個沒有參數沒有返回值的函數,而set_new_handler函數是一個輸入參數為 operator new 分配內存失敗時調用的出錯處理函數的指針,返回值為set_new_handler函數沒調用之前的已經在起作用的舊的函數處理函數的指針。使用方法如下:
~~~
void noMoreMemory(){
cerr<<"123";
abort();
}
int main(){
set_new_handler(noMoreMemory);
int *p = new int [ 0x1fffffff ];
~~~
真正在程序中,new_handler函數不是簡單的檢測出錯然后停止程序,而是要去處理出錯,即通過一些措施,使下次內存分配可以成功。一個好的new_handler函數從以下幾個方向入手:
1.產生更多的可用內存。若在程序啟動時預留一大塊內存。然后失敗時釋放出這些內存供使用。
2.安裝另一個不同版本的new_handler函數。通過一個新的可以獲得資源的函數來獲得更多資源,或者使用一些全局變量來改變自身的行為。
3.卸載new_handler。分配無法成功,直接返回拋出標準的std::bad_alloc異常。
4.拋出bad_alloc異常或bad_alloc的派生異常。
5.沒有返回,即如上所示,調用abort或exit。
類中的內存分配出錯可以有自己重載的類的new操作符和 set_new_handler來實現。而這些函數與重載都是類的靜態成員。
首先,類中要有一個靜態成員 currentHandler來保存當前使用的new_handler函數。
對于set_new_handler,它只是將類內的指針進行更換,以及返回舊的指針,而標準版本也是如此做的。
對于operator new ,類中的重載new操作符,只是簡單的調用原有的new操作符操作,只是錯誤處理用上類中設置的新的出錯處理函數,所以其做法只是用全局的set_new_handler()設置當前類中的currentHandler為處理函數,分配內存后或者異常報錯前,恢復即可。
代碼如下:
~~~
class A{
public:
static void* operator new (size_t size);
static new_handler set_new_handler(new_handler p);
private:
static new_handler currentHandler;
};
new_handler A::currentHandler;
new_handler A::set_new_handler(new_handler p){
new_handler t = currentHandler;
currentHandler = p;
return t;
}
void* A::operator new(size_t size){
new_handler globalHandler = std::set_new_handler(currentHandler);
void* memory;
try{
memory = ::operator new(size);//::operator new 表示為全局的new操作符。
}
catch(std::bad_alloc&){
std::set_new_handler(globalHandler);
throw;
}
std::set_new_handler(globalHandler);
return memory;
}
~~~
而要做到很好的代碼重用,即對任何一個類都能輕松的實現以上代碼實現的內容,可以創建一個混合風格的基類,這種基類允許子類繼承它的某一特定功能,如這里的new操作符的出錯處理。
混合風格?43.