int *pi = new(std::nothrow) int 的用法

 

咱們都知道,使用 malloc/calloc 等分配內存的函數時,必定要檢查其返回值是否爲「空指針」(亦即檢查分配內存的操做是否成功),這是良好的編程習慣,也是編寫可靠程序所必需的。可是,若是你簡單地把這一招應用到 new 上,那可就不必定正確了。我常常看到相似這樣的代碼:c++

        int* p = new int[SIZE];
        if ( p == 0 ) // 檢查 p 是否空指針
            return -1;
        // 其它代碼程序員

    其實,這裏的 if ( p == 0 ) 徹底是沒啥意義的。C++ 裏,若是 new 分配內存失敗,默認是拋出異常的。因此,若是分配成功,p == 0 就絕對不會成立;而若是分配失敗了,也不會執行 if ( p == 0 ),由於分配失敗時,new 就會拋出異常跳事後面的代碼。若是你想檢查 new 是否成功,應該捕捉異常編程

        try {
            int* p = new int[SIZE];
            // 其它代碼
        } catch ( const bad_alloc& e ) {
            return -1;
        }函數

    聽說一些老的編譯器裏,new 若是分配內存失敗,是不拋出異常的(大概是由於那時 C++ 還沒加入異常機制),而是和 malloc 同樣,返回空指針。不過我歷來都沒遇到過 new 返回空指針的狀況。spa

    固然,標準 C++ 亦提供了一個方法來抑制 new 拋出異常,而返回空指針:設計

        int* p = new (std::nothrow) int; // 這樣若是 new 失敗了,就不會拋出異常,而是返回空指針
        if ( p == 0 ) // 如此這般,這個判斷就有意義了
            return -1;
        // 其它代碼指針

===============================詳解===================================code

首先按c++標準的話,new失敗會拋出bad_alloc異常,可是有些編譯器對c++標準支持不是很好,好比vc++6.0中new失敗不會拋出異常,而返回0.對象

//不支持c++標準的作法以下繼承

double *ptr=new double[1000000];

if( 0 == ptr)

……處理失敗……

//標準推薦作法一。

try

{

    double *ptr=new double[1000000];

}

catch(bad_alloc &memExp)

{

    //失敗之後,要麼abort要麼重分配

    cerr<<memExp.what()<<endl;

}

 

//標準推薦作法二

是使用set_new_handler函數處理new失敗。它在頭文件<new>裏大體是象下面這樣定義的:

typedef void (*new_handler)();

new_handler set_new_handler(new_handler p) throw();

能夠看到,new_handler是一個自定義的函數指針類型,它指向一個沒有輸入參數也沒有返回值的函數。set_new_handler則是一個輸入並返回new_handler類型的函數。

set_new_handler的輸入參數是operator new分配內存失敗時要調用的出錯處理函數的指針,返回值是set_new_handler沒調用以前就已經在起做用的舊的出錯處理函數的指針。

 

能夠象下面這樣使用set_new_handler:

// function to call if operator new can't allocate enough memory

void nomorememory()

{

cerr << "unable to satisfy request for memory\n";

abort();

}

 

int main()

{

set_new_handler(nomorememory);

int *pbigdataarray = new int[100000000];

 

...

 

}

operator new不能知足內存分配請求時,new-handler函數不僅調用一次,而是不斷重複,直至找到足夠的內存。實現重複調用的代碼在條款8裏能夠看到,這裏我用描述性的的語言來講明:一個設計得好的new-handler函數必須實現下面功能中的一種。

    ·產生更多的可用內存。這將使operator new下一次分配內存的嘗試有可能得到成功。實施這一策略的一個方法是:在程序啓動時分配一個大的內存塊,而後在第一次調用new-handler時釋放。釋放時伴隨着一些對用戶的警告信息,如內存數量太少,下次請求可能會失敗,除非又有更多的可用空間。

    ·安裝另外一個不一樣的new-handler函數。若是當前的new-handler函數不能產生更多的可用內存,可能它會知道另外一個new-handler函數能夠提供更多的資源。這樣的話,當前的new-handler能夠安裝另外一個new-handler來取代它(經過調用set_new_handler)。下一次operator new調用new-handler時,會使用最近安裝的那個。(這一策略的另外一個變通辦法是讓new-handler能夠改變它本身的運行行爲,那麼下次調用時,它將作不一樣的事。方法是使new-handler能夠修改那些影響它自身行爲的靜態或全局數據。)

    ·卸除new-handler。也就是傳遞空指針給set_new_handler。沒有安裝new-handler,operator new分配內存不成功時就會拋出一個標準的std::bad_alloc類型的異常。

    ·拋出std::bad_alloc或從std::bad_alloc繼承的其餘類型的異常。這樣的異常不會被operator new捕捉,因此它們會被送到最初進行內存請求的地方。(拋出別的不一樣類型的異常會違反operator new異常規範。規範中的缺省行爲是調用abort,因此new-handler要拋出一個異常時,必定要確信它是從std::bad_alloc繼承來的。想更多地瞭解異常規範)

    ·沒有返回。典型作法是調用abort或exit。abort/exit能夠在標準c庫中找到(還有標準c++庫)。

 

上面的選擇給了你實現new-handler函數極大的靈活性。

更多知識請參考《Effective c++》

————————————————————————————————————————————————————————————

 

建議30:new內存失敗後的正確處理

應該有不少的程序員對比爾蓋茨的這句話有所耳聞:

對於任何一我的而言,640KB應當是足夠的了。(640K ought to be enough for everybody.)

不幸的是,偉大的比爾蓋茨也失言了。隨着硬件水平的發展,內存變得愈來愈大,可是彷佛仍不能知足人們對內存日益增加的需求。因此呢,咱們C/C++程序員在寫程序時也必須考慮一下內存申請失敗時的處理方式。

一般,咱們在使用new進行內存分配的時候,會採用如下的處理方式:

  1. char *pStr = new string[SIZE];  
  2. if(pStr == NULL)  
  3. {  
  4.     ... // Error processing  
  5.       return false;  

你能發現上述代碼中存在的問題嗎?這是一個隱蔽性極強的臭蟲(Bug)。

咱們沿用了C時代的良好傳統:使用 malloc 等分配內存的函數時,必定要檢查其返回值是否爲「空指針」,並以此做爲檢查分配內存操做是否成功的依據,這種Test-for-NULL代碼形式是一種良好的編程習慣,也是編寫可靠程序所必需的。但是,這種完美的處理形式必須有一個前提:若new失敗,其返回值必須是NULL。只有這樣才能保證上述看似「邏輯正確、風格良好」的代碼能夠正確運行。

那麼new失敗後編譯器究竟是怎麼處理的?在好久以前,即C++編譯器的蠻荒時代,C++編譯器保留了C編譯器的處理方式:當operator new不能知足一個內存分配請求時,它返回一個NULL 指針。這曾經是對C的malloc函數的合理擴展。然而,隨着技術的發展,標準的更新,編譯器具備了更強大的功能,類也被設計得更漂亮,新時代的new在申請內存失敗時具有了新的處理方式:拋出一個bad_alloc exception(異常)。因此,在新的標準裏,上述Test-for-NULL處理方式再也不被推薦和支持。

若是再回頭看看本建議開頭的代碼片斷,其中的 if (pStr == 0 )從良好的代碼風格忽然一下變成了毫無心義。在C++裏,若是 new 分配內存失敗,默認是拋出異常。因此,若是分配成功,pStr == 0就絕對不會成立;而若是分配失敗了,也不會執行if ( pStr == 0 ),由於分配失敗時,new 就會拋出異常並跳事後面的代碼。

爲了更加明確地理解其中的玄機,首先看看相關聲明:

  1. namespace   std  
  2. {  
  3.      class   bad_alloc  
  4.      {  
  5.              //   ...  
  6.      };  
  7. }  
  8.  
  9. //   new   and   delete  
  10. void   *operator   new(std::size_t)   throw(std::bad_alloc);  
  11. void   operator   delete(void   *)   throw();  
  12.  
  13. //   array   new   and   delete  
  14. void   *operator   new[](std::size_t)   throw(std::bad_alloc);  
  15. void   operator   delete[](void   *)   throw();  
  16.  
  17. //   placement   new   and   delete  
  18. void   *operator   new(std::size_t,   void   *)   throw();  
  19. void   operator   delete(void   *,   void   *)   throw();  
  20.  
  21. //   placement   array   new   and   delete  
  22. void   *operator   new[](std::size_t,   void   *)   throw();  
  23. void   operator   delete[](void   *,   void   *)   throw(); 

 

在以上的new操做族中,只有負責內存申請的operator new纔會拋出異常std::bad_alloc。若是出現了這個異常,那就意味着內存耗盡,或者有其餘緣由致使內存分配失敗。因此,按照C++標準,若是想檢查 new 是否成功,則應該捕捉異常:

  1. try  
  2. {  
  3.     int* pStr = new string[SIZE];  
  4. ...  // processing codes  
  5. }  
  6. catch ( const bad_alloc& e )  
  7. {  
  8. return -1;  

可是市面上還存在着一些古老編譯器的蹤影,這些編譯器並不支持這個標準。同時,在這個標準制定以前已經存在的不少代碼,若是由於標準的改變而變得漏洞百出,確定會引發不少人抗議。C++標準化委員會並不想遺棄這些 Test-for-NULL的代碼,因此他們提供了operator new 的另外一種可選形式— nothrow ,用以提供傳統的Failure-yields-NULL行爲。

其實現原理以下所示:

  1. void * operator new(size_t cb, const std::nothrow_t&) throw()  
  2. {  
  3.  char *p;  
  4.  try  
  5.  {  
  6.     p = new char[cb];  
  7.  }  
  8.  catch (std::bad_alloc& e)  
  9.  {  
  10.     p = 0;  
  11.  }  
  12.  return p;  

 

文件中也聲明瞭nothrow new的重載版本,其聲明方式以下所示:

  1. namespace   std  
  2. {  
  3.      struct   nothrow_t  
  4.      {  
  5.         //   ...  
  6.      };  
  7.      extern   const   nothrow_t   nothrow;  
  8. }  
  9.  
  10. //  new   and   delete  
  11. void   *operator   new(std::size_t,   std::nothrow_t   const   &)   throw();  
  12. void   operator   delete(void   *,   std::nothrow_t   const   &)   throw();  
  13.  
  14. //   array   new   and   delete  
  15. void   *operator   new[](std::size_t,   std::nothrow_t   const   &)   throw();  
  16. void   operator   delete[](void   *,   std::nothrow_t   const   &)   throw(); 

 

若是採用不拋出異常的new形式,本建議開頭的代碼片斷就應該改寫爲如下形式:

  1. int* pStr = new(std::nothrow) string[SIZE];  
  2. if(pStr==NULL)  
  3. {  
  4.     ...  // 錯誤處理代碼  

根據建議29可知,編譯器在表達式 new (std::nothrow) ClassName中一共完成了兩項任務。首先,operator new 的 nothrow 版本被調用來爲一個ClassName object分配對象內存。假如這個分配失敗,operator new返回null指針;假如內存分配成功,ClassName 的構造函數則被調用,而在此刻,對象的構造函數就能作任何它想作的事了。若是此時它也須要new 一些內存,可是沒有使用 nothrow new形式,那麼,雖然在"new (std::nothrow) ClassName" 中調用的operator new 不會拋出異常,但其構造函數卻無心中辦了件錯事。假如它真的這樣作了,exception就會像被普通的operator new拋出的異常同樣在系統裏傳播。因此使用nothrow new只能保證operator new不會拋出異常,沒法保證"new (std::nothrow) ClassName"這樣的表達式不會拋出exception。因此,慎用nothrow new。

最後還須要說明一個比較特殊可是確實存在的問題:在Visual C++ 6.0 中目前operator new、operator new(std::nothrow) 和 STL 之間不兼容、不匹配,並且不能徹底被修復。若是在非MFC項目中使用Visual C++6.0中的STL,其即裝即用的行爲可能致使STL在內存不足的狀況下讓應用程序崩潰。對於基於MFC的項目,STL是否可以倖免於難,徹底取決於你使用的 STL 針對operator new的異常處理。這一點,在James Hebben的文章《不要讓內存分配失敗致使您的舊版 STL 應用程序崩潰》中進行了詳細的介紹,若是你在使用古老的Visual C++ 6.0編譯器,並且對這個問題充滿興趣,請Google之。

請記住:

當使用new申請一塊內存失敗時,拋出異常std::bad_alloc是C++標準中規定的標準行爲,因此推薦使用try{ p = new int[SIZE]; } catch( std::bad_alloc ) { ... }的處理方式。可是在一些老舊的編譯器中,卻不支持該標準,它會返回NULL,此時具備C傳統的Test_for_NULL代碼形式便起了做用。因此,要針對不一樣的情形採起合理的處置方式。

相關文章
相關標籤/搜索