程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> Item 51:寫new和delete時請遵循慣例

Item 51:寫new和delete時請遵循慣例

編輯:C++入門知識

Item 51:寫new和delete時請遵循慣例


Item 51: Adhere to convention when writing new and delete.

Item 50介紹了如何自定義newdelete但沒有解釋你必須遵循的慣例, 這些慣例中有些並不直觀,所以你需要記住它們!

  • operator new需要無限循環地獲取資源,如果沒能獲取則調用”new handler”,不存在”new handler”時應該拋出異常;
  • operator new應該處理size == 0的情況;
  • operator delete應該兼容空指針;
  • operator new/delete作為成員函數應該處理size > sizeof(Base)的情況(因為繼承的存在)。

    外部operator new

    Item 49指出了如何將operator new重載為類的成員函數,在此我們先看看如何實現一個外部(非成員函數)的operator newoperator new應當有正確的返回值,在內存不足時應當調用”new handler”,請求申請大小為0的內存時也可以正常執行,避免隱藏全局的(”normal form”)new

    • 給出返回值很容易。當內存足夠時,返回申請到的內存地址;當內存不足時,根據Item 49描述的規則返回空或者拋出bad_alloc異常。
    • 每次失敗時調用”new handler”,並重復申請內存卻不太容易。只有當”new handler”為空時才應拋出異常。
    • 申請大小為零時也應返回合法的指針。允許申請大小為零的空間確實會給編程帶來方便。

      考慮到上述目標,一個非成員函數的operator new大致實現如下:

      void * operator new(std::size_t size) throw(std::bad_alloc){
          if(size == 0) size = 1;
          while(true){
              // 嘗試申請
              void *p = malloc(size);
      
              // 申請成功
              if(p) return p;
      
              // 申請失敗,獲得new handler
              new_handler h = set_new_handler(0);
              set_new_handler(h);
      
              if(h) (*h)();
              else throw bad_alloc();
          }
      }
      
      • size == 0時申請大小為1看起來不太合適,但它非常簡單而且能正常工作。況且你不會經常申請大小為0的空間吧?
      • 兩次set_new_handler調用先把全局”new handler”設置為空再設置回來,這是因為無法直接獲取”new handler”,多線程環境下這裡一定需要鎖。
      • while(true)意味著這可能是一個死循環。所以Item 49提到,”new handler”要麼釋放更多內存、要麼安裝一個新的”new handler”,如果你實現了一個無用的”new handler”這裡就是死循環了。

        成員operator new

        重載operator new為成員函數通常是為了對某個特定的類進行動態內存管理的優化,而不是用來給它的子類用的。 因為在實現Base::operator new()時,是基於對象大小為sizeof(Base)來進行內存管理優化的。

        當然,有些情況你寫的Base::operator new是通用於整個class及其子類的,這時這一條規則不適用。

        class Base{
        public:
            static void* operator new(std::size_t size) throw(std::bad_alloc);
        };
        class Derived: public Base{...};
        
        Derived *p = new Derived;       // 調用了 Base::operator new !
        

        子類繼承Base::operator new()之後,因為當前對象不再是假設的大小,該方法不再適合管理當前對象的內存了。 可以在Base::operator new中判斷參數size,當大小不為sizeof(Base)時調用全局的new

        void *Base::operator new(std::size_t size) throw(std::bad_alloc){
            if(size != sizeof(Base)) return ::operator new(size);
            ...
        }
        

        上面的代碼沒有檢查size == 0!這是C++神奇的地方,大小為0的獨立對象會被插入一個char(見Item 39)。 所以sizeof(Base)永遠不會是0,所以size == 0的情況交給::operator new(size)去處理了。

        這裡提一下operator new[],它和operator new具有同樣的參數和返回值, 要注意的是你不要假設其中有幾個對象,以及每個對象的大小是多少,所以不要操作這些還不存在的對象。因為:

        1. 你不知道對象大小是什麼。上面也提到了當繼承發生時size不一定等於sizeof(Base)
        2. size實參的值可能大於這些對象的大小之和。因為Item 16中提到,數組的大小可能也需要存儲。

          外部operator delete

          相比於new,實現delete的規則要簡單很多。唯一需要注意的是C++保證了delete一個NULL總是安全的,你尊重該慣例即可。

          同樣地,先實現一個外部(非成員)的delete

          void operator delete(void *rawMem) throw(){
              if(rawMem == 0) return; 
              // 釋放內存
          }
          

          成員operator delete

          成員函數的delete也很簡單,但要注意如果你的new轉發了其他size的申請,那麼delete也應該轉發其他size的申請。

          class Base{
          public:
              static void * operator new(std::size_t size) throw(std::bad_alloc);
              static void operator delete(void *rawMem, std::size_t size) throw();
          };
          void Base::operator delete(void *rawMem, std::size_t size) throw(){
              if(rawMem == 0) return;     // 檢查空指針
              if(size != sizeof(Base)){
                  ::operator delete(rawMem);
              }
              // 釋放內存
          }
          

          注意上面的檢查的是rawMem為空,size是不會為空的。

          其實size實參的值是通過調用者的類型來推導的(如果沒有虛析構函數的話):

          Base *p = new Derived;  // 假設Base::~Base不是虛函數
          delete p;               // 傳入`delete(void *rawMem, std::size_t size)`的`size == sizeof(Base)`。
          

          如果Base::~Base()聲明為virtual,則上述size就是正確的sizeof(Derived)。 這也是為什麼Item 7指出析構函數一定要聲明virtual

           

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