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

Item 49:new handler的行為

編輯:關於C++

Item 49: Understand the behavior of the new-handler.

new申請內存失敗時會拋出"bad alloc"異常,此前會調用一個由std::set_new_handler()指定的錯誤處理函數(”new-handler”)。

set_new_handler()

“new-handler”函數通過std::set_new_handler()來設置,std::set_new_handler()定義在中:

namespace std{
    typedef void (*new_handler)();
    new_handler set_new_handler(new_handler p) throw();
}

throw()是一個異常聲明,表示不拋任何異常。例如void func() throw(Exception1, Exception2)表示func可能會拋出Exception1Exception2兩種異常。

set_new_handler()的使用也很簡單:

void outOfMem(){
    std::cout<<"Unable to alloc memory";
    std::abort();
}
int main(){
    std::set_new_handler(outOfMem);
    int *p = new int[100000000L];
}

new申請不到足夠的內存時,它會不斷地調用outOfMem。因此一個良好設計的系統中outOfMem函數應該做如下幾件事情之一:

  • 使更多內存可用;
  • 安裝一個新的”new-handler”;
  • 卸載當前”new-handler”,傳遞nullset_new_handler即可;
  • 拋出bad_alloc(或它的子類)異常;
  • 不返回,可以abort或者exit

    類型相關錯誤處理

    std::set_new_handler設置的是全局的bad_alloc的錯誤處理函數,C++並未提供類型相關的bad_alloc異常處理機制。 但我們可以重載類的operator new,當創建對象時暫時設置全局的錯誤處理函數,結束後再恢復全局的錯誤處理函數。

    比如Widget類,首先需要聲明自己的set_new_handleroperator 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 current;
    };
    
    // 靜態成員需要定義在類的外面
    std::new_handler Widget::current = 0;
    std::new_handler Widget::set_new_handler(std::new_handler p) throw(){
        std::new_handler old = current;
        current = p;
        return old;
    }
    

    關於abort,exit,terminate的區別:abort會設置程序非正常退出,exit會設置程序正常退出,當存在未處理異常時C++會調用terminate, 它會回調由std::set_terminate設置的處理函數,默認會調用abort

    最後來實現operator new,該函數的工作分為三個步驟:

    1. 調用std::set_new_handler,把Widget::current設置為全局的錯誤處理函數;
    2. 調用全局的operator new來分配真正的內存;
    3. 如果分配內存失敗,Widget::current將會拋出異常;
    4. 不管成功與否,都卸載Widget::current,並安裝調用Widget::operator new之前的全局錯誤處理函數。

      重載operator new

      我們通過RAII類來保證原有的全局錯誤處理函數能夠恢復,讓異常繼續傳播。關於RAII可以參見Item 13。 先來編寫一個保持錯誤處理函數的RAII類:

      class NewHandlerHolder{
      public:
          explicit NewHandlerHolder(std::new_handler nh): handler(nh){}
          ~NewHandlerHolder(){ std::set_new_handler(handler); }
      private:
          std::new_handler handler;
          NewHandlerHolder(const HandlerHolder&);     // 禁用拷貝構造函數
          const 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(current));
          return ::operator new(size);    // 調用全局的new,拋出異常或者成功
      }   // 函數調用結束,原有錯誤處理函數恢復
      

      使用Widget::operator new

      客戶使用Widget的方式也符合基本數據類型的慣例:

      void outOfMem();
      Widget::set_new_handler(outOfMem);
      
      Widget *p1 = new Widget;    // 如果失敗,將會調用outOfMem
      string *ps = new string;    // 如果失敗,將會調用全局的 new-handling function,當然如果沒有的話就沒有了
      Widget::set_new_handler(0); // 把Widget的異常處理函數設為空
      Widget *p2 = new Widget;    // 如果失敗,立即拋出異常
      

      通用基類

      仔細觀察上面的代碼,很容易發現自定義”new-handler”的邏輯其實和Widget是無關的。我們可以把這些邏輯抽取出來作為一個模板基類:

      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 current;
      };
      
      template
      std::new_handler NewHandlerSupport::current = 0;
      
      template
      std::new_handler NewHandlerSupport::set_new_handler(std::new_handler p) throw(){
          std::new_handler old = current;
          current = p;
          return old;
      }
      
      template
      void * NewHandlerSupport::operator new(std::size_t size) throw(std::bad_alloc){
          NewHandlerHolder h(std::set_new_handler(current));
          return ::operator new(size);
      }
      

      有了這個模板基類後,給Widget添加”new-handler”支持只需要public繼承即可:

      class Widget: public NewHandlerSupport{ ... };
      

      其實NewHandlerSupport的實現和模板參數T完全無關,添加模板參數是因為handler是靜態成員,這樣編譯器才能為每個類型生成一個handler實例。

      nothrow new

      1993年之前C++的operator new在失敗時會返回null而不是拋出異常。如今的C++仍然支持這種nothrow的operator new

      Widget *p1 = new Widget;    // 失敗時拋出 bad_alloc 異常
      assert(p1 != 0);            // 這總是成立的
      
      Widget *p2 = new (std::nothrow) Widget;
      if(p2 == 0) ...             // 失敗時 p2 == 0
      

      “nothrow new” 只適用於內存分配錯誤。而構造函數也可以拋出的異常,這時它也不能保證是new語句是”nothrow”的。

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