C++中的new操作符首先使用operator new函數來配置設定空間,然後再在此空間上調用類的構造函數構造對象。當operator new無法配置設定所需的記憶體空間時,預設的情況下會抛出一個bad_alloc異常,在抛出這個異常之前,如果使用者指定了錯誤處理函數即new_handler,則程式會先執行new_handler函數進行錯誤處理。為了設定這個錯誤處理函數,我們需要調用set_new_handler函數,它在std命名空間内的情況如下所示
namespace std{
typedef void (*new_handler)();
new_handler set_new_handler(new_handler p)throw ();
}
set_new_handler函數承若不抛出任何異常。該函數接受一個new_handler函數指針,且傳回一個這樣的指針。pNew代表的是如果operator new發生失敗,采用的錯誤處理函數是pNew所指向的函數,set_new_handler函數傳回的是以前的錯誤處理函數指針。比如,
void myErrorHandler(){
cerr<<"can't need the request for memory";
abort();
}
int main(){
std::set_new_handler(myErrorHandler);
int *ptr=new int[1000000000L];
delete [] ptr;
return ;
}
當出現無法滿足記憶體配置設定要求的時候,程式會不斷的調用myErrorHandler函數進行記憶體的配置設定,如果錯誤處理函數中沒有abort(),終端會不斷的列印錯誤提示。設定全局的set_new_handler,隻要出現operator new失敗,都會調用該錯誤處理函數。但是如果我們需要給不同的類設定不同的錯誤處理函數時,該怎麼辦呢?也就是說我們需要定制類自己的set_new_handler函數。
class Widget{
public:
static std::new_handler set_new_handler(std::new_handler pNew) throw ();
static void* operator new(std::size_t size)throw (std::bad_alloc);
private:
static std::new_handler currentHandler;
int array[100000000L];//為了容易讓new Widget出現異常
};
std::new_handler Widget::currentHandler=0;
如上所示,類Widget需要定義自己的new_handler變量,而且需要定義自己的set_new_handler函數和operator new函數,set_new_handler函數比較簡單,參照全局的set_new_handler函數的行為即可。
std::new_handler Widget::set_new_handler(std::new_handler pNew){
std::new_handler oldHandler=currentHandler;
currentHandler=pNew;
return oldHandler;
}
函數set_new_handler設定新的new_handler,并且傳回以前的new_handler。
那麼Widget類中的operator new函數應該怎麼定義呢?
首先,它應該設定本類的new_handler,怎麼給Widget設定自己的new_handler并讓其正常工作呢?調用std::set_new_handler即可,類中的currentHandler可作為其參數;
其次,調用std::operator new函數請求配置設定記憶體,但是,這裡可能存在一個資源洩露的問題,如第一步,我們将全局的set_new_handler設定成類Widget的錯誤處理函數,如果配置設定失敗,我們怎麼将原來的new_handler再設定回去呢?很顯然的一種方法就是通過對象來管理new_handler,如下
class NewHandlerHolder{
public:
explicit NewHandlerHolder(new_handler p):oldHandler(p){}
~NewHandlerHoler(){
std::set_new_handler(oldHander);
}
private:
std::new_handler oldHandler;
NewHandlerHolder(const NewHandlerHolder&);
NewHandlerHolder& operator=(const NewHandlerHolder&);
};
這是一個資源管理類,如果我們定義了一個NewHandlerHolder對象來管理原來的new_handler,當記憶體配置設定失敗時,抛出異常且函數退棧,同時也會調用已經構造的對象的析構函數,當我們調用NewHandlerHolder的析構函數時,會重新将原來的new_handler設定回去。用于資源管理的類在較多情況下不允許進行指派和複制構造的動作,因為我們希望這個資源管理的對象具有資源唯一的管理權。
此時,我們就可以寫出來Widget類中的operator new函數了。
void* Widget::operator new(std::size_t size) throw (std::bad_alloc){
NewHandlerHolder temp(std::set_new_handler(currentHandler));
return ::operator new(size);
}
定義完所有所需的内容後,我們可以使用定制的new_handler。
int main(){
Widget::set_new_handler(myErrorHandler);
//Widget::set_new_handler(0);
while(1)
Widget *ptr=new Widget;
}
運作結果如下圖
将注釋的解注,運作結果沒有"can't need the request for memory"。
以上思想就能為各個類編寫自己的定制的new_handler,但是為每個需要編寫定制new_handler的類寫如此相似的代碼,似乎有點不合理,為了避免代碼重複,我們可以使用模闆
template<typename T>
class NewHandlerSupport{
public:
static std::new_handler set_new_handler(std::new_handler newHandler)throw ();
static void* operator new(std::size_t size)throw (std::bad_alloc);
private:
static std::new_handler currentHandler;
};
template<typename T>
void* NewHandlerSupport<T>::operator new(std::size_t size){
NewHandlerHolder temp(std::set_new_handler(currentHandler));
return ::operator new(size);
}
template<typename T>
std::new_handler NewHandlerSupport<T>::set_new_handler(std::new_handler newHandler)throw(){
std::new_handler oldHander=currentHandler;
currentHandler=newHandler;
return oldHandler;
}
template<typename T>
std::new_handler NewHandlerSupport<T>::currentHandler=0;
如果我們希望類widget獲得定制的new_handler行為,隻需要将widget類以public繼承NewHandlerSupport<widget>即可。我們隻是希望獲得不同模闆參數所帶來的不同的static std::new_handler currentHandler,我們需要用它來定制各個類的new_handler。