正文

回到頂部

1. new-handler介紹

當(dāng)操作符new不能滿足內(nèi)存分配請(qǐng)求的時(shí)候,它就會(huì)拋出異常。很久之前,它會(huì)返回一個(gè)null指針,一些舊的編譯器仍然會(huì)這么做。你仍然會(huì)看到這種舊行為,但是我會(huì)把關(guān)于它的討論推遲到本條款結(jié)束的時(shí)候。

1.1 調(diào)用set_new_handler來(lái)指定全局new-handler

在operator new由于不能滿足內(nèi)存分配要求而拋出異常之前,它會(huì)調(diào)用一個(gè)客戶指定的叫做new-handler的錯(cuò)誤處理函數(shù)。(這也不是完全正確的。Operator new的真正行為更加復(fù)雜。詳細(xì)內(nèi)容在Item 51中描述。)為了指定內(nèi)存溢出處理(out-of-memory-handling)函數(shù),客戶可以調(diào)用set_new_handler函數(shù),這個(gè)標(biāo)準(zhǔn)庫(kù)函數(shù)被聲明在<new>中:

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

 

正如你所看到的,new_handler是一個(gè)函數(shù)指針的typedef,這個(gè)函數(shù)沒(méi)有參數(shù)沒(méi)有返回值,set_new_handler是一個(gè)參數(shù)和返回值都為new_handler的函數(shù)。(函數(shù)set_new_handler聲明結(jié)束處的”throw()”是一個(gè)異常指定(exception specification)。從本質(zhì)上來(lái)說(shuō)它的意思是說(shuō)這個(gè)函數(shù)不會(huì)拋出任何異常,然而事實(shí)更加有意思。詳細(xì)內(nèi)容見(jiàn)Item 29。)

set_new_handler的參數(shù)是指向函數(shù)的指針,operator new會(huì)在請(qǐng)求的內(nèi)存無(wú)法分配的情況下調(diào)用這個(gè)函數(shù)。Set_new_handler的返回值也是指向函數(shù)的指針,返回的是在調(diào)用set_new_handler之前調(diào)用的new_handler函數(shù)(也就是在new_handler被替換之前的函數(shù))。

你可以像下面這樣使用set_new_handler:

seo優(yōu)化培訓(xùn),網(wǎng)絡(luò)推廣培訓(xùn),網(wǎng)絡(luò)營(yíng)銷培訓(xùn),SEM培訓(xùn),網(wǎng)絡(luò)優(yōu)化,在線營(yíng)銷培訓(xùn)

 1 // function to call if operator new can’t allocate enough memory 2 void outOfMem() 3 { 4 std::cerr << "Unable to satisfy request for memory\n"; 5 std::abort(); 6 } 7  8 int main() 9 {10 std::set_new_handler(outOfMem);11 int *pBigDataArray = new int[100000000L];12 ...13 }

seo優(yōu)化培訓(xùn),網(wǎng)絡(luò)推廣培訓(xùn),網(wǎng)絡(luò)營(yíng)銷培訓(xùn),SEM培訓(xùn),網(wǎng)絡(luò)優(yōu)化,在線營(yíng)銷培訓(xùn)

 

如果operaotr new無(wú)法為100,000,000個(gè)整數(shù)分配內(nèi)存,就會(huì)調(diào)用outOfMem,也就是輸出一個(gè)error信息之后程序終止(abort)。(順便說(shuō)一下,考慮在向cerr中寫(xiě)入error信息期間如果必須動(dòng)態(tài)的分配內(nèi)存會(huì)發(fā)生什么。。)

1.2 如何設(shè)計(jì)一個(gè)良好的new-handler函數(shù)

當(dāng)operator new不能滿足一個(gè)內(nèi)存請(qǐng)求的時(shí)候,它會(huì)反復(fù)調(diào)用new-handler函數(shù)直到它發(fā)現(xiàn)有足夠的內(nèi)存可以分配了。引起這些函數(shù)被反復(fù)調(diào)用的代碼在Item 51中可以找到,但是這種高級(jí)別的描述信息足夠讓我們得出結(jié)論:一個(gè)設(shè)計(jì)良好的new-handler函數(shù)必須能夠做到如下幾點(diǎn)。

  • 提供更多的可被使用的內(nèi)存。這可以保證下次在operator new內(nèi)部嘗試分配內(nèi)存時(shí)能夠成功。實(shí)現(xiàn)這個(gè)策略的一種方法是在程序的開(kāi)始階段分配一大塊內(nèi)存,然后在第一次調(diào)用new-handler的時(shí)候釋放它。

  • 安裝一個(gè)不同的new-handler。如果當(dāng)前的new-handler不能夠?yàn)槟闾峁└嗟膬?nèi)存,可能另外一個(gè)new-handler可以。如果是這樣,可以在當(dāng)前的new-handler的位置上安裝另外一個(gè)new-handler(通過(guò)調(diào)用set_new_handler)。下次operator new調(diào)用new-handler函數(shù)的時(shí)候,它會(huì)調(diào)用最近安裝的。(這個(gè)主題的一個(gè)變種是一個(gè)使用new_handler來(lái)修改它自己的行為,所以在下次觸發(fā)這個(gè)函數(shù)的時(shí)候,它就會(huì)做一些不同的事情。達(dá)到這個(gè)目的的一個(gè)方法是讓new_handler修改影響new-handler行為的static數(shù)據(jù),命名空間數(shù)據(jù)或者全局?jǐn)?shù)據(jù)。)

  • 卸載new-handler,也就是為set_new_handler傳遞null指針。如果沒(méi)有安裝new-handler,operator  new在內(nèi)存分配失敗的時(shí)候會(huì)拋出異常。

  • 沒(méi)有返回值,調(diào)用abort或者exit。

這些選擇讓你在實(shí)現(xiàn)new-handler的時(shí)候有相當(dāng)大的靈活性。

回到頂部

2. 為特定類指定new-handler

有時(shí)候你想用不同方式來(lái)處理內(nèi)存分配失敗,這依賴于需要分配內(nèi)存的對(duì)象所屬的類:

seo優(yōu)化培訓(xùn),網(wǎng)絡(luò)推廣培訓(xùn),網(wǎng)絡(luò)營(yíng)銷培訓(xùn),SEM培訓(xùn),網(wǎng)絡(luò)優(yōu)化,在線營(yíng)銷培訓(xùn)

 1 class X { 2 public: 3 static void outOfMemory(); 4 ... 5 }; 6 class Y { 7 public: 8 static void outOfMemory(); 9 ...10 };11 X* p1 = new X; // if allocation is unsuccessful,12 // call X::outOfMemory13 Y* p2 = new Y; // if allocation is unsuccessful,14 // call Y::outOfMemory

seo優(yōu)化培訓(xùn),網(wǎng)絡(luò)推廣培訓(xùn),網(wǎng)絡(luò)營(yíng)銷培訓(xùn),SEM培訓(xùn),網(wǎng)絡(luò)優(yōu)化,在線營(yíng)銷培訓(xùn)

 

C++沒(méi)有為類提供指定的new-handlers,但也不需要。你可以自己實(shí)現(xiàn)這種行為。你可以使每個(gè)類提供自己版本的set_new_handler和operator new。類中的set_new_handler允許客戶為類提供new_handler(就像標(biāo)準(zhǔn)的set_new_handler允許客戶指定全局的new-handler一樣)。類的operator new確保為類對(duì)象分配內(nèi)存時(shí),會(huì)使用其指定的new-handler來(lái)替代全局new-handler。

2.1 在類中聲明static new_handler成員

假設(shè)你想對(duì)Widget類對(duì)象的內(nèi)存分配失敗做一下處理。當(dāng)operator new不能為Widget對(duì)象分配足夠的內(nèi)存的時(shí)候你必須跟蹤一下函數(shù)調(diào)用過(guò)程,所以你要聲明一個(gè)類型為new_handler的static成員,來(lái)指向這個(gè)類的new-handler函數(shù)。Widget將會(huì)是下面這個(gè)樣子:

seo優(yōu)化培訓(xùn),網(wǎng)絡(luò)推廣培訓(xùn),網(wǎng)絡(luò)營(yíng)銷培訓(xùn),SEM培訓(xùn),網(wǎng)絡(luò)優(yōu)化,在線營(yíng)銷培訓(xùn)

1 class Widget {2 public:3 static std::new_handler set_new_handler(std::new_handler p) throw();4 static void* operator new(std::size_t size) throw(std::bad_alloc);5 private:6 static std::new_handler currentHandler;7 };

seo優(yōu)化培訓(xùn),網(wǎng)絡(luò)推廣培訓(xùn),網(wǎng)絡(luò)營(yíng)銷培訓(xùn),SEM培訓(xùn),網(wǎng)絡(luò)優(yōu)化,在線營(yíng)銷培訓(xùn)

 

靜態(tài)類成員必須在類外部定義(除非他們是const整型,見(jiàn)Item 2),所以:

1 std::new_handler Widget::currentHandler = 0; // init to null in the class2 // impl. File

 

Widget中的set_new_handler函數(shù)會(huì)把傳遞進(jìn)去的指針(所指向的new-handler函數(shù))保存起來(lái),并且會(huì)返回調(diào)用set_new_handler之前所保存的指針。這也是標(biāo)準(zhǔn)版本set_new_handler的做法:

seo優(yōu)化培訓(xùn),網(wǎng)絡(luò)推廣培訓(xùn),網(wǎng)絡(luò)營(yíng)銷培訓(xùn),SEM培訓(xùn),網(wǎng)絡(luò)優(yōu)化,在線營(yíng)銷培訓(xùn)

1 std::new_handler Widget::set_new_handler(std::new_handler p) throw()2 {3 std::new_handler oldHandler = currentHandler;4 currentHandler = p;5 return oldHandler;6 }

seo優(yōu)化培訓(xùn),網(wǎng)絡(luò)推廣培訓(xùn),網(wǎng)絡(luò)營(yíng)銷培訓(xùn),SEM培訓(xùn),網(wǎng)絡(luò)優(yōu)化,在線營(yíng)銷培訓(xùn)

 

2.2 重新定義operator new

最后,Widget的operator new將會(huì)做下面的事情:

  1. 調(diào)用標(biāo)準(zhǔn)set_new_handler,參數(shù)為Widget的錯(cuò)誤處理函數(shù)。這就將Widget的new-handler安裝成為了全局的new-handler。

  2. 調(diào)用全局的operator new來(lái)執(zhí)行實(shí)際的內(nèi)存分配。如果分配失敗,全局的operator new會(huì)觸發(fā)Widget的new-handler,因?yàn)檫@個(gè)函數(shù)已經(jīng)被安裝為全局new-handler。如果全局的operator new最終不能分配內(nèi)存,它會(huì)拋出bad_alloc異常。在這種情況下,Widget的operator new必須恢復(fù)原來(lái)的全局new-handler,然后傳播異常。為了確保源new-handler總是能被恢復(fù),Widget將全局new-handler作為資源來(lái)處理,遵循Item 13的建議,使用資源管理對(duì)象來(lái)防止資源泄漏。

  3. 如果全局operator new能夠?yàn)閃idget對(duì)象分配足夠的內(nèi)存。Widget的operator new就會(huì)返回指向被分配內(nèi)存的指針。管理全局new-handler的對(duì)象的析構(gòu)函數(shù)會(huì)自動(dòng)恢復(fù)調(diào)用Widget的operator new之前的new-handler。

這里我們以資源處理(resource-handling)類開(kāi)始,只包含基本的RAII處理操作,包括在構(gòu)造時(shí)獲取資源和在在析構(gòu)時(shí)釋放資源(Item 13):

seo優(yōu)化培訓(xùn),網(wǎng)絡(luò)推廣培訓(xùn),網(wǎng)絡(luò)營(yíng)銷培訓(xùn),SEM培訓(xùn),網(wǎng)絡(luò)優(yōu)化,在線營(yíng)銷培訓(xùn)

 1 class NewHandlerHolder { 2 public: 3 explicit NewHandlerHolder(std::new_handler nh) // acquire current 4 : handler(nh) {} // new-handler 5  6 ~NewHandlerHolder()                             // release it 7  8 { std::set_new_handler(handler); }           
 9 10 private:                                                    
11 12  13 14 std::new_handler handler;                             // remember it15 16 NewHandlerHolder(const NewHandlerHolder&);      // prevent copying17 18  19 20 NewHandlerHolder&                                   // (see Item 14)21 22 operator=(const NewHandlerHolder&);     
23 24 };

seo優(yōu)化培訓(xùn),網(wǎng)絡(luò)推廣培訓(xùn),網(wǎng)絡(luò)營(yíng)銷培訓(xùn),SEM培訓(xùn),網(wǎng)絡(luò)優(yōu)化,在線營(yíng)銷培訓(xùn)

                                                      

 這會(huì)使得Widget的operator new的實(shí)現(xiàn)非常簡(jiǎn)單:

seo優(yōu)化培訓(xùn),網(wǎng)絡(luò)推廣培訓(xùn),網(wǎng)絡(luò)營(yíng)銷培訓(xùn),SEM培訓(xùn),網(wǎng)絡(luò)優(yōu)化,在線營(yíng)銷培訓(xùn)

 1 void* Widget::operator new(std::size_t size) throw(std::bad_alloc) 2 { 3 NewHandlerHolder // install Widget’s 4 h(std::set_new_handler(currentHandler)); // new-handler 5  6 return ::operator new(size);        // allocate memory 7 // or throw 8  9 }                                                  // restore global10 // new-handler11 12 13 14 void outOfMem();                                 // decl. of func. to call if mem. alloc.15 // for Widget objects fails16 17 Widget::set_new_handler(outOfMem); // set outOfMem as Widget’s18 // new-handling function19 20 Widget *pw1 = new Widget;                 // if memory allocation21 // fails, call outOfMem22 23 std::string *ps = new std::string;           // if memory allocation fails,24 // call the global new-handling25 // function (if there is one)26 27 Widget::set_new_handler(0);                // set the Widget-specific28 // new-handling function to29 // nothing (i.e., null)30 31 Widget *pw2 = new Widget;                 // if mem. alloc. fails, throw an32 // exception immediately. (There is33 // no new- handling function for34 // class Widget.)

seo優(yōu)化培訓(xùn),網(wǎng)絡(luò)推廣培訓(xùn),網(wǎng)絡(luò)營(yíng)銷培訓(xùn),SEM培訓(xùn),網(wǎng)絡(luò)優(yōu)化,在線營(yíng)銷培訓(xùn)

 

2.3 將NewHandlerHolder轉(zhuǎn)換為模板

不管在什么類中,實(shí)現(xiàn)的這個(gè)主題的代碼都是一樣的,所以我們可以為其設(shè)一個(gè)合理的目標(biāo),就是代碼能夠在其他地方重用。達(dá)到這個(gè)目標(biāo)的一個(gè)簡(jiǎn)單方法是創(chuàng)建一個(gè)“混合風(fēng)格(mixin-style)”的基類,也就是設(shè)計(jì)一個(gè)基類,允許派生類繼承單一特定的能力——在這個(gè)例子中,這種能力就是為類指定new-handler。然后將基類變?yōu)橐粋€(gè)模板,于是你可以為每個(gè)繼承類獲得一份不同的類數(shù)據(jù)的拷貝。

這個(gè)設(shè)計(jì)的基類部分使得派生類能夠繼承它們都需要的set_new_handler和operator new函數(shù),同時(shí)設(shè)計(jì)的模板部分確保每個(gè)繼承類獲得一個(gè)不同的currentHandler數(shù)據(jù)成員。說(shuō)起來(lái)有些復(fù)雜,但是代碼看上去很熟悉。事實(shí)上,唯一真正不一樣的是現(xiàn)在任何類都能夠獲得這個(gè)功能:

seo優(yōu)化培訓(xùn),網(wǎng)絡(luò)推廣培訓(xùn),網(wǎng)絡(luò)營(yíng)銷培訓(xùn),SEM培訓(xùn),網(wǎng)絡(luò)優(yōu)化,在線營(yíng)銷培訓(xùn)

 1 template<typename T> // “mixin-style” base class for 2 class NewHandlerSupport { // class-specific set_new_handler 3 public: // support 4 static std::new_handler set_new_handler(std::new_handler p) throw(); 5 static void* operator new(std::size_t size) throw(std::bad_alloc); 6 ... // other versions of op. new — 7 // see Item 52 8 private: 9 static std::new_handler currentHandler;10 };11 template<typename T>12 std::new_handler13 NewHandlerSupport<T>::set_new_handler(std::new_handler p) throw()14 {15 std::new_handler oldHandler = currentHandler;16 currentHandler = p;17 return oldHandler;18 }19 template<typename T>20 void* NewHandlerSupport<T>::operator new(std::size_t size)21 throw(std::bad_alloc)22 {23 NewHandlerHolder h(std::set_new_handler(currentHandler));24 return ::operator new(size);25 }26 // this initializes each currentHandler to null27 template<typename T>28 std::new_handler NewHandlerSupport<T>::currentHandler = 0;

seo優(yōu)化培訓(xùn),網(wǎng)絡(luò)推廣培訓(xùn),網(wǎng)絡(luò)營(yíng)銷培訓(xùn),SEM培訓(xùn),網(wǎng)絡(luò)優(yōu)化,在線營(yíng)銷培訓(xùn)

 

有了這個(gè)類模板之后,向Widget中添加set_new_handler支持就變得容易了:Widget只需要繼承自NewHandlerSupport<Widget>。(這可能看上去比較獨(dú)特,接下來(lái)我會(huì)進(jìn)行詳細(xì)的解釋。)

1 class Widget: public NewHandlerSupport<Widget> {2 ... // as before, but without declarations for3 4 };                             // set_new_handler or operator new

 

這是Widget提供一個(gè)特定的set_new_handler需要做的所有事情。

但是對(duì)于Widget繼承自NewHandlerSupport<Widget>,你可能還是有些不安。如果是這樣,當(dāng)你注意到NewHandlerSupport模板永遠(yuǎn)不會(huì)使用類型參數(shù)T之后你的不安可能會(huì)加劇。你沒(méi)有必要這樣。對(duì)于每個(gè)繼承自NewHandlerSupport的類來(lái)說(shuō),我們所有需要的是一份不同的NewHandlerSupport的拷貝——特別是靜態(tài)數(shù)據(jù)成員currentHandler的不同拷貝。模板機(jī)制自身會(huì)為每個(gè)T自動(dòng)生成currentHandler的一份拷貝,NewHandlerSupport使用這個(gè)T來(lái)進(jìn)行實(shí)例化。

對(duì)于Widget繼承自一個(gè)使用Widget作為類型參數(shù)的模板基類來(lái)說(shuō),如果這個(gè)概念讓你感覺(jué)眩暈,不要感覺(jué)不好。每個(gè)人看到開(kāi)始看到它的時(shí)候都會(huì)有這種感覺(jué)。但是,它是非常有用的技術(shù),它有一個(gè)名字,這個(gè)名字如果這個(gè)概念一樣,第一次看到它的人沒(méi)有人會(huì)感覺(jué)它很自然,它叫做怪異的循環(huán)模板模式(curiously recurring template pattern CRTP)。

我曾經(jīng)寫(xiě)過(guò)一遍文章建議為它起一個(gè)更好的名字:do it for me,因?yàn)楫?dāng)Widget繼承自NewHandlerSupport<Widget>,它真的像是在說(shuō):“我是Widget,我需要為Widget繼承NewHandlerSupport類“。沒(méi)有人使用我建議的名字,但是使用“do it for me”來(lái)想象一下CRTP可能會(huì)幫助你理解模板化的繼承會(huì)做什么。

有了像NewHandlerSupport這樣的模板,為任何需要new-hadler的類添加一個(gè)特定的new-handler就會(huì)變得容易?;旌巷L(fēng)格的繼承總是會(huì)將你引入多繼承的主題,在開(kāi)始進(jìn)入這個(gè)主題之前,你可能想讀一下Item 40

回到頂部

3. Nothrow版本的new

直到1993年,當(dāng)不能滿足分配內(nèi)存的要求時(shí),C++要求operator new要返回null?,F(xiàn)在指定operator new要拋出bad_alloc異常,但是大量的C++是在編譯器支持修訂版本之前寫(xiě)出來(lái)的。C++標(biāo)準(zhǔn)委員會(huì)也不想廢棄test-for-null的代碼,所以它們?yōu)閛perator new提供了一種替代形式,它能夠提供傳統(tǒng)的“失敗產(chǎn)生null(failure-yields-null)”行為。這些形式被叫做“nothrow”形式,某種程度上是因?yàn)樗麄兪褂昧瞬粫?huì)拋出異常的對(duì)象(定義在頭文件<new>中),new在這種情況下被使用:

seo優(yōu)化培訓(xùn),網(wǎng)絡(luò)推廣培訓(xùn),網(wǎng)絡(luò)營(yíng)銷培訓(xùn),SEM培訓(xùn),網(wǎng)絡(luò)優(yōu)化,在線營(yíng)銷培訓(xùn)

 1 class Widget { ... }; 2 Widget *pw1 = new Widget;                        // throws bad_alloc if 3 // allocation fails 4  5 if (pw1 == 0) ...                                             // this test must fail 6  7 Widget *pw2 = new (std::nothrow) Widget;   // returns 0 if allocation for 8 // the Widget fails 9 10 if (pw2 == 0) ...                                             // this test may succeed

seo優(yōu)化培訓(xùn),網(wǎng)絡(luò)推廣培訓(xùn),網(wǎng)絡(luò)營(yíng)銷培訓(xùn),SEM培訓(xùn),網(wǎng)絡(luò)優(yōu)化,在線營(yíng)銷培訓(xùn)

 

nothrow版本的new不會(huì)像從表面上看起來(lái)這樣可靠,對(duì)于異常它沒(méi)有提供讓人信服的保證。對(duì)于表達(dá)式“new (std::nothrow) Widget”,會(huì)發(fā)生兩件事情。首先,通過(guò)調(diào)用nothrow版本的operator new來(lái)為一個(gè)Widget 對(duì)象分配足夠的內(nèi)存。如果分配失敗了,operator new會(huì)返回null指針。然而如果分配成功了,Widget構(gòu)造函數(shù)會(huì)被調(diào)用,到這個(gè)時(shí)候,就會(huì)世事難料了。Widget構(gòu)造函數(shù)能夠做任何它想做的。它自己可能new一些內(nèi)存,如果是這樣,并沒(méi)有強(qiáng)迫它使用nothrow版本的new。雖然在”new (std::nothrow) Widget”中的operator new不會(huì)拋出異常,但是Widget構(gòu)造函數(shù)卻可能拋出來(lái)。如果是這樣,異常會(huì)像平時(shí)一樣傳播出去。結(jié)論是什么?使用nothrow new只能保證operator new不會(huì)拋出異常,不能保證像“new(std::nothrow) Widget”這樣的表達(dá)式不拋出異常。十有八九,你將永遠(yuǎn)不會(huì)有使用nothrow new的需要。

不論你是使用”普通的”(也就是拋出異常的)new還是nothrow版本的new,重要的是你需要明白new-handler的行為,因?yàn)樵趦煞Nnew中都會(huì)使用到它。

回到頂部

4. 總結(jié)

  • Set_new_handler允許你在分配內(nèi)存不能滿足要求的時(shí)候指定一個(gè)特定的被調(diào)用的函數(shù)。

  • Nothrow new功能有限,因?yàn)樗荒鼙粦?yīng)用在內(nèi)存分配上;相關(guān)聯(lián)的構(gòu)造函數(shù)調(diào)用可能仍然會(huì)拋出異常。


作者: HarlanC 

博客地址: http://www.cnblogs.com/harlanc/ 
個(gè)人博客: http://www.harlancn.me/ 
本文版權(quán)歸作者和博客園共有,歡迎轉(zhuǎn)載,但未經(jīng)作者同意必須保留此段聲明,且在文章頁(yè)面明顯位置給出, 原文鏈接 

如果覺(jué)的博主寫(xiě)的可以,收到您的贊會(huì)是很大的動(dòng)力,如果您覺(jué)的不好,您可以投反對(duì)票,但麻煩您留言寫(xiě)下問(wèn)題在哪里,這樣才能共同進(jìn)步。謝謝! 

http://www.cnblogs.com/harlanc/p/6721163.html