程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> Effective C++ 條款 49:了解new-handler的行為

Effective C++ 條款 49:了解new-handler的行為

編輯:C++入門知識

Effective C++ 條款 49:了解new-handler的行為


(一)

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。如果當前的new-handler函數不能申請足夠內存,則可以使用set_new_handler替換為其他的合適版本。卸除new-handler,即將空指針傳遞給set_new_handler。一旦沒有安裝任何new-handler,operator new會在內在分配失敗時拋出異常。拋出bad_alloc(或其派生類對象)的異常。這樣的異常不會被operator new捕捉,因此會被傳播到內存申請處。不返回,通常調用abort()或exit()。

    (四)

    為支持類專屬的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做以下事情:
      調用標准set_new_handler,告訴該類的錯誤處理函數,這會將該類的new-handler安裝為全局的new-handler。調用全局的operator new,執行實際的內存分配。如果分配失敗,全局的operator new會調用該類的new-handler,因為那個函數剛剛被安裝為全局的new-handler。如果全局的new-handler最終無法分配足夠內存,會拋出bad_alloc異常,在此情況下該類的operator new必須恢復原來的全局new-handler,然後再傳播該異常。為確保原來的new-handler總是能夠被重新安裝,該類將全局new-handler視為“資源”並使用資源管理對象來防止資源洩漏。如果全局operator new能夠分配足夠的內存,會返回一個指針指向分配的地址。該類的析構函數會管理全局new-handler,它會自動將該類operator new被調用前的那個全局new-handler恢復回來。
    (五)
    實例:從資源處理類開始,只有基礎性的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 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);
    }
    因為對於class裡面的static成員變量的初始化必須要在class的外面定義。

    所以以下是將每一個currentHandler初始化為null

    template
    std::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是一個頗為局限的工具,因為它只適用於內存分配;後繼的構造函數調用還是有可能拋出異常的。








  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved