重載new/delete要遵循的規則

來源:互聯網
上載者:User
條款8: 寫operator new和operator delete時要遵循常規

 

自己重寫operator new時(條款10解釋了為什麼有時要重寫它),很重要的一點是函數提供的行為要和系統預設的operator new一致。實際做起來也就是:要有正確的傳回值;可用記憶體不夠時要調用出錯處理函數(見條款7);處理好0位元組記憶體請求的情況。此外,還要避免不小心隱藏了標準形式的new,不過這是條款9的話題。

有關傳回值的部分很簡單。如果記憶體配置請求成功,就返回指向記憶體的指標;如果失敗,則遵循條款7的規定拋出一個std::bad_alloc類型的異常。

但事情也不是那麼簡單。因為operator new實際上會不只一次地嘗試著去分配記憶體,它要在每次失敗後調用出錯處理函數,還期望出錯處理函數能想辦法釋放別處的記憶體。只有在指向出錯處理函數的指標為空白的情況下,operator new才拋出異常。

另外,c++標準要求,即使在請求分配0位元組記憶體時,operator new也要返回一個合法指標。(實際上,這個聽起來怪怪的要求確實給c++語言其它地方帶來了簡便)

這樣,非類成員形式的operator new的虛擬碼看起來會象下面這樣:

void * operator new(size_t size)        // operator new還可能有其它參數
{                                       

  if (size == 0) {                      // 處理0位元組請求時,
    size = 1;                           // 把它當作1個位元組請求來處理
  }                                     
  while (1) {
    分配size位元組記憶體;

    if (分配成功)
      return (指向記憶體的指標);

    // 分配不成功,找出當前出錯處理函數
    new_handler globalhandler = set_new_handler(0);
    set_new_handler(globalhandler);

    if (globalhandler) (*globalhandler)();
    else throw std::bad_alloc();
  }
}

處理零位元組請求的技巧在於把它作為請求一個位元組來處理。這看起來也很怪,但簡單,合法,有效。而且,你又會多久遇到一次零位元組請求的情況呢?

你又會奇怪上面的虛擬碼中為什麼把出錯處理函數置為0後又立即恢複。這是因為沒有辦法可以直接得到出錯處理函數的指標,所以必須通過調用set_new_handler來找到。辦法很笨但也有效。

條款7提到operator new內部包含一個無限迴圈,上面的代碼清楚地說明了這一點——while (1)將導致無限迴圈。跳出迴圈的唯一辦法是記憶體配置成功或出錯處理函數完成了條款7所描述的事件中的一種:得到了更多的可用記憶體;安裝了一個新的new-handler(出錯處理函數);卸載了new-handler;拋出了一個std::bad_alloc或其衍生類別型的異常;或者返回失敗。現在明白了為什麼new-handler必須做這些工作中的一件。如果不做,operator new裡面的迴圈就不會結束。

很多人沒有認識到的一點是operator new經常會被子類繼承。這會導致某些複雜性。上面的虛擬碼中,函數會去分配size位元組的記憶體(除非size為0)。size很重要,因為它是傳遞給函數的參數。但是大多數針對類所寫的operator new(包括條款10中的那種)都是只為特定的類設計的,不是為所有的類,也不是為它所有的子類設計的。這意味著,對於一個類x的operator new來說,函數內部的行為在涉及到對象的大小時,都是精確的sizeof(x):不會大也不會小。但由於存在繼承,基類中的operator new可能會被調用去為一個子類對象分配記憶體:

class base {
public:
  static void * operator new(size_t size);
  ...
};

class derived: public base       // derived類沒有聲明operator new
{ ... };

derived *p = new derived;        // 調用base::operator new

如果base類的operator new不想費功夫專門去處理這種情況——這種情況出現的可能性不大——那最簡單的辦法是把這個“錯誤”數量的記憶體配置請求轉給標準operator new來處理,象下面這樣:

void * base::operator new(size_t size)
{
  if (size != sizeof(base))             // 如果數量“錯誤”,讓標準operator new
    return ::operator new(size);        // 去處理這個請求
                                        // 

  ...                                   // 否則處理這個請求
}

“停!”我聽見你在叫,“你忘了檢查一種雖然不合理但是有可能出現的一種情況——size有可能為零!”是的,我沒檢查,但拜託下次再叫出聲的時候不要這麼文縐縐的。:)但實際上檢查還是做了,只不過融合到size != sizeof(base)語句中了。c++標準很怪異,其中之一就是規定所以獨立的(freestanding)類的大小都是非零值。所以sizeof(base)永遠不可能是零(即使base類沒有成員),如果size為零,請求會轉到::operator new,由它來以一種合理的方式對請求進行處理。(有趣的是,如果base不是獨立的類,sizeof(base)有可能是零,詳細說明參見"my article on counting objects")。

如果想控制基於類的數組的記憶體配置,必須實現operator new的數組形式——operator new[](這個函數常被稱為“數組new”,因為想不出"operator new[]")該怎麼發音)。寫operator new[]時,要記住你面對的是“原始”記憶體,不能對數組裡還不存在的對象進行任何操作。實際上,你甚至還不知道數組裡有多少個對象,因為不知道每個對象有多大。基類的operator new[]會通過繼承的方式被用來為子類對象的數組分配記憶體,而子類對象往往比基類要大。所以,不能想當然認為base::operator new[]裡的每個對象的大小都是sizeof(base),也就是說,數組裡對象的數量不一定就是(請求位元組數)/sizeof(base)。關於operator new[]的詳細介紹參見條款m8。

重寫operator new(和operator new[])時所有要遵循的常規就這些。對於operator delete(以及它的夥伴operator delete[]),情況更簡單。所要記住的只是,c++保證刪除null 指標永遠是安全的,所以你要充分地應用這一保證。下面是非類成員形式的operator delete的虛擬碼:

void operator delete(void *rawmemory)
{
  if (rawmemory == 0) return;    //如果指標為空白,返回

  釋放rawmemory指向的記憶體;
  return;
}

這個函數的類成員版本也簡單,只是還必須檢查被刪除的對象的大小。假設類的operator new將“錯誤”大小的分配請求轉給::operator new,那麼也必須將“錯誤”大小的刪除請求轉給::operator delete:

class base {                       // 和前面一樣,只是這裡聲明了
public:                            // operator delete
  static void * operator new(size_t size);
  static void operator delete(void *rawmemory, size_t size);
  ...
};

void base::operator delete(void *rawmemory, size_t size)
{
  if (rawmemory == 0) return;      // 檢查null 指標

  if (size != sizeof(base)) {      // 如果size"錯誤",
    ::operator delete(rawmemory);  // 讓標準operator來處理請求
    return;                        
  }

  釋放指向rawmemory的記憶體;

  return;
}

可見,有關operator new和operator delete(以及他們的數組形式)的規定不是那麼麻煩,重要的是必須遵守它。只要記憶體配置程式支援new-handler函數並正確地處理了零記憶體請求,就差不多了;如果記憶體釋放程式又處理了null 指標,那就沒其他什麼要做的了。至於在類成員版本的函數裡增加繼承支援,那將很快就可以完成。

出處:《Effective C++》
轉自:http://www.leftworld.net/online/effectivec/file/ch02c.htm

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.