(一)
new_handler函數:當operator new或operator new[]分配內存失敗時調用的函數。
set_new_handler函數:試圖分配更多內存以足夠使用,成功時會返回,否則會拋出一個bad_alloc異常(或其派生類對象)或調用cstdlib的abort()或exit()函數強制退出。
(二)
當operator new分配內存失敗(不足)時,會先調用一個客戶指定的錯誤處理函數new-handler,然後拋出一個異常(舊式的行為是返回一個空指針)。客戶使用set_new_handler來設定該行為,它是聲明於
namespace std{ typedef void(*new_handler)();//指向函數的指針 new_handler set_new_handler(new_handler p) throw();//獲得分配內存失敗時應該被調用的函數指針,並返回本函數在調用前正在執行(但馬上就要被替換)的new-handler函數指針 }
當operator new無法滿足內存申請時,會不斷調用new-handler函數直到內存夠用。注:operator new諸函數與new不同,前者是標准程序庫中的實現,它是一個函數,但不是重載,而後者是一個表達式。
(三)
一個設計良好的new-handler函數必須做以下事情:
(四)
為支持類專屬的new-handler,可以在類中聲明需要的new_handler函數,並提供類專屬的set_new_handler(指定new-handler)和operator
new(確保在分配類對象內存的過程中以該類專屬的new-handler替換全局的new-handler)版本。具體過程為:
(1)聲明一個類型為new_handler的靜態成員函數,用以指向該類的new-handler。例如:
class Widget { public: static new_handler set_new_handler(new_handler p) throw(); static void* operator new(size_t size) throw(bad_alloc); private: static new_handler currentHandler; };
注意static成員必須在類外定義(除非是const且是int型),所以需要這麼寫:
std::new_handler Widget::currentHandler = 0;(2)set_new_handler函數將它獲得的指針存儲起來,然後返回調用前存儲的指針(與標准set_new_handler)相同:std::new_handler Widget::set_new_handler(std::new_handler p) throw() { std::new_handler oldHandler = currentHandler; currendHandler = p; return oldHandler; }(3)使該類的operator new做以下事情:
(五)
實例:從資源處理類開始,只有基礎性的RAII操作,在構造過程中獲得一筆資源,並在析構過程中歸還:
class NewHandlerHolder { public: explicit NewHandlerHolder(std::new_handler nh) : handler(nh) {} //取得目前的new-handler ~NewHandlerHolder() { set_new_handler(handler); } //釋放new-handler private: std::new_handler handler; //保存new-handler NewHandlerHolder(const NewHandlerHolder&); //阻止復制行為 NewHandlerHolder& operator= (const NewHandlerHolder&); };
假設有一個類Widget,它實現operator new的方法:
void* Widget::operator new(std::size_t size) throw(std::bad_alloc) { NewHandlerHolder h(std::set_new_Handler(currentHandler)); //安裝Widget的new-handler return ::operator new(size); //分配內存或拋出異常,恢復全局new-handler }
Widget的客戶應該類似如下方式new-handler:
void outOfMem(); //聲明內存分配失敗時的處理函數 Widget::set_new_handler(outOfMem); //將上面的函數設定為Widget的new-handler函數 Widget* pw1 = new Widget; //如果內存分配失敗,調用outOfMem() std::string* ps = new std::string;//如果內存分配失敗,調用全局的new-handler函數(如果有的話) Widget::set_new_handler(0); //設定Widget專屬的new-handler為空,失去專屬版本 Widget* pw2 = new Widget; //如果內存分配失敗,則拋出異常
可以將上述方法抽象,建立mixin風格的基類,它允許派生類繼承單一特定能力(這裡是“設定該類專屬的new-handler“),並把該基類抽象為模板類以提高復用性。
代碼如下:
template因為對於class裡面的static成員變量的初始化必須要在class的外面定義。class NewHandlerSupport { public: static std::new_handler set_new_handler(std::new_handler p) throw(); static void* operator new(std::size_t size) throw(std::bad_alloc); private: static std::new_handler currentHandler; }; template std::new_handler NewHandlerHolder ::set_new_handler(std::new_handler p) throw() { std::new_handler oldHandler = currentHandler; currentHandler = p; return oldHandler; } template void* NewHandlerSupport ::operator new(std::size_t size) throw(std::bad_alloc){ NewHandlerHolder h(std::set_new_handler(currentHandler)); return ::operator new(size); }
所以以下是將每一個currentHandler初始化為null
templatestd::new_handler NewHandlerSupport ::currentHandler = 0;
對於一個具體的類,添加專屬的set_new_handler就很容易了:繼承。
class Widget : public NewHandlerSupport{ ... //和先前一樣,但不必聲明set_new_handler或operator new };
6.舊式的operator new在內存分配失敗後返回null,為了兼容舊代碼,C++提供了另
一形式的operator new,稱為nothrow形式,在new使用的場合使用了nothrow對象(定義於頭文件中)。建議不要使用。因為如果構造函數也拋出異常的話,那樣的話會產生遞歸發生異常。
請記住:
(1)set_new_handler允許客戶指定一個函數,在內存分配無法獲得滿足時被調用。
(2)Nothrow new是一個頗為局限的工具,因為它只適用於內存分配;後繼的構造函數調用還是有可能拋出異常的。