讀書筆記_Effective_C++_條款四十九:了解new_handler的行為


本章開始討論內存分配的一些用法,C/C++內存分配采用new和delete。在new申請內存時,可能會遇到的一種情況就是,內存不夠了,這時候會拋出out of memory的異常。有的時候,我們希望能夠調用自己定制的異常處理函數,這就是本條款要說的。

 

在聲明於<new>的一個標准程序庫中,有如下的接口:

1 namespace std
2 {
3     typedef void (*new_handler)();
4     new_handler set_new_handler(new handler p) throw();
5 }

注意這里面typedef了一個函數指針new_handler,它指向一個函數,這個函數的返回值為void,形參也是void。set_new_handler就是將new_handler指向具體的函數,在這個函數里面處理out of memory異常(函數末尾的throw()表示它不拋出任務異常),如果這個new_handler為空,那么這個函數沒有執行,就會拋出out of memory異常。

 1 void MyOutOfMemory()
 2 {
 3     cout << "Out of memory error!" << endl;
 4     abort();
 5 }
 6 
 7 int main()
 8 {
 9     set_new_handler(MyOutOfMemory);
10     int *verybigmemory = new int[0x1fffffff];
11     delete verybigmemory;
12 }

這里預先設定好new異常時調用的函數為MyOutOfMemory,然后故意申請一個很大的內存,就會走到MyOutOfMemory中來了。

 

好,我們更進一步,現在想要在不同的類里面定制不同的new_handler處理機制,一種想法是在類內部定義set_new_handler函數,將new_handler作為私有的成員變量,具體的new_handler函數可以由構造函數傳入,但編譯器要求set_new_handler是靜態的,所以通過構造函數傳入new_handler不被編譯器支持,只能將set_new_handler與operator new都寫成靜態的,同時定義一個靜態的new_handler變量,像下面這樣:

 1 class Widget
 2 {
 3 private:
 4     static new_handler CurrentHandler;
 5 
 6 public:
 7     void set_new_handler(new_handler h) throw()
 8     {
 9         CurrentHandler = h;
10     }
11     static void* operator new(size_t size)
12     {
13         Widget::set_new_handler(CurrentHandler);
14         return ::operator new(size);
15     }
16 };
17 
18 new_handler Widget::CurrentHandler = 0;

 

屬於類的靜態變量CurrentHandler用於保存當前環境下的new_handler函數,在operator_new中,先設置成當前的new異常處理函數,再去調用std的operator new,執行內存分配操作。但這里就存在問題了,set_new_handler到下一次設置它為止,一直都是生效的,我們只想在處理這個類對象的分配時用自定義的new_handler函數,但是類似於new int,new char這些基本類型,還是希望走默認的new_handler(就是null,就是什么也不執行,如我們期望,這樣會拋出異常)。

一種自然的想法,就是在調用operator new末尾處還原new_handler,這就需要保存之前的new_handler,為此,我們構造一個NewHandlerHolder類,像下面這樣:

 1 class NewHandlerHolder
 2 {
 3 private:
 4     new_handler SavedHandler;
 5     NewHandlerHolder(const NewHandlerHolder&);
 6     NewHandlerHolder& operator= (const NewHandlerHolder&);
 7 
 8 public:
 9     explicit NewHandlerHolder(new_handler h) :SavedHandler(h){}
10     ~NewHandlerHolder()
11     {
12         set_new_handler(SavedHandler);
13     }
14 };

這里有一個SavedHandler成員變量,它在NewHandlerHolder構造時確定具體的指向(其實就是指向系統默認的new_handler函數(即null),將拷貝構造函數與賦值運算符重載設置為private是為了防止出現拷貝的行為(在編譯期就可以阻止),這點可以參照之前的條款。還要特別注意這里的析構函數,它調用了set_new_handler,將new異常的處理恢復成SavedHandler(其實就是null)。

 

這樣我們重新整理一下Widget,如下:

 1 class Widget
 2 {
 3 private:
 4     static new_handler CurrentHandler;
 5 
 6 
 7 public:
 8     static new_handler set_new_handler(new_handler h) throw()
 9     {
10         new_handler OldHandler = CurrentHandler;
11         CurrentHandler = h;
12         return OldHandler;
13     }
14     static void* operator new(size_t size)
15     {
16         NewHandlerHolder h(Widget::set_new_handler(CurrentHandler));
17         return ::operator new(size);
18     }
19 };
20 
21 new_handler Widget::CurrentHandler = 0;

為了返回系統默認的new_handler,我們在set_new_handler處理完之后,進行了舊handler的返回,同時在operator new的調用中進行了NewHandlerHolder的包裝,這樣在return之后,h會自動調用析構函數,恢復成默認的new_handler。

 

到這一步,本條款的重要內容已經說完了,但為了避免重復勞動,即為每一個需要重定義new_handler的類都寫一份set_new_handler和operator new,書上在最后對之進行了封裝,其實就是將Widget專門作為這兩個函數(set_new_handler和operator new)的類,然后將需要自行處理new_handler的類public繼承於Widget即可。

 

最后總結一下:

set_new_handler允許客戶指定一個函數,在內存分配無法獲得滿足時被調用。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM