當 operator new 無法滿足記憶體請求時,會拋出 std::bad_alloc 異常 (舊編譯器可能回傳 null)。拋出前,會呼叫用戶可設定的 new-handler 函式。透過 <new> 標頭的 std::set_new_handler 設定:
namespace std {
typedef void (*new_handler)();
new_handler set_new_handler(new_handler p) throw();
}
void outOfMem() {
std::cerr << "Unable to satisfy request for memory\n";
std::abort();
}
int main() {
std::set_new_handler(outOfMem);
int* pBigDataArray = new int[100000000L];
...
}
set_new_handler 替換自己,下一次嘗試會呼叫新 handler。set_new_handler,後續配置失敗直接拋出異常。bad_alloc 或衍生類別,傳播給呼叫端。abort() 或 exit() 終止程式。operator new 會反覆呼叫 handler 直到配置成功或 handler 不返回/拋出異常。
C++ 不直接支援 per-class handler,但可自行實作。讓每個類別提供自己的 set_new_handler 與 operator new:
class Widget {
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;
};
關鍵實作:使用 RAII 物件 NewHandlerHolder 確保全域 handler 在配置前後正確切換與還原:
class NewHandlerHolder {
public:
explicit NewHandlerHolder(std::new_handler nh) : handler(nh) {}
~NewHandlerHolder() { std::set_new_handler(handler); }
private:
std::new_handler handler;
// 禁止拷貝
};
void* Widget::operator new(std::size_t size) throw(std::bad_alloc) {
NewHandlerHolder h(std::set_new_handler(currentHandler)); // 安裝 Widget 專屬 handler
return ::operator new(size); // 呼叫全域 operator new
} // 解構時自動還原全域 handler
為避免重複撰寫,將上述邏輯封裝為模板基類 NewHandlerSupport<T>,利用 Curiously Recurring Template Pattern (CRTP) 使每個衍生類別擁有獨立的 currentHandler:
template<typename T>
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;
};
// Widget 只需繼承即可獲得功能
class Widget : public NewHandlerSupport<Widget> { ... };
模板參數 T 僅用於區分類別,編譯器會為每個實例化產生獨立的靜態成員。這項技術被戲稱為 "Do It For Me" 模式。