天天看點

定制類自己的的new_handler

  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;
}      

  運作結果如下圖

定制類自己的的new_handler

   将注釋的解注,運作結果沒有"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。