effective C++ 筆記-02-內存管理01

條款5:對應的new和delete要採用相同的形式

下面的語句有什麼錯?
string *stringArray = new string[100];
    ...
delete stringArray;


stringArray指向的100個string對象中的99個不會被正確地摧毀,因爲他們的析構函數永遠不會被調用。用new的時候會發生兩件事。首先,內存被分配,然後,爲被分配的內存調用一個或多個構造函數。用delete的時候,也有兩件事發生:首先,爲將被釋放的內存調用一個或多個析構函數,然後,釋放內存。
    string *stringPtr1 = new string;
    string *stringPtr2 = new string[100];
    ...
    delete stringPtr1;           // 刪除一個對象
    delete [] stringPtr2;        // 刪除對象數組

如果你在stringPtr1前加了"[]"會怎樣呢?答案是:那將是不可預測的;如果你沒在stringPtr2前沒加上"[]"又會怎樣呢?答案也是:不可預測。所以,解決這類問題的規則很簡單:如果你調用new時用了[],調用delete時也要用[]。如果調用new時沒有用[],那調用delete時也不要用[]這個規則對喜歡用typedef的人來說也很重要,因爲寫typedef 的程序員必須告訴別人,用new創建了一個typedef 定義的類型的對象後,該用什麼形式的delete來刪除。

typedef string AddressLines[4];   //  一個人的地址,共4行,
                                  //每行一個string
string *pal = new AddressLines;   // 注意"new AddressLines"
                                  //返回string*, 和
                                  // "new string[4]"返回的一樣
delete pal;                       // 錯誤!
delete [] pal;                    //  正確

爲了避免混亂,最好杜絕對數組類型用typedefs。這其實很容易,因爲標準C++庫(見條款49)包含有stirng和vector模板,使用他們將會使對數組的需求減少到幾乎零。

條款6:析構函數裏對指針成員調用delete


如果在構造函數裏忘了初始化某個指針,或者在賦值操作的過程中忘了處理它,問題會出現得很快,很明顯,所以在實踐中這兩個問題不會那麼折磨你。但是,如果在析構函數裏沒有刪除指針,它不會表現出很明顯的外部症狀。相反,它可能只是表現爲一點微小的內存泄露,並且不斷增長,最後吞噬了你的地址空間,導致程序夭折。因爲這種情況經常不那麼引人注意,所以每增加一個指針成員到類裏時一定要記清楚。

刪除空指針是安全的,在你的析構函數裏你就可以只用簡單地delete掉他們,而不用擔心他們是不是被new過。

你當然不會用delete去刪除一個沒有用new來初始化的指針,你也永遠不會去刪除一個傳遞給你的指針。換句話說,除非類成員最初用了new,否則是不用在析構函數裏用delete的。
說到智能指針,這裏介紹一種避免必須刪除指針成員的方法,即把這些成員用智能指針對象來代替。

條款7:預先準備好內存不夠的情況

 
operator new在無法完成內存分配請求時會拋出異常。你有時會不去管它,也許一直沒去管它。但你心裏一定還是深深地隱藏着一種罪惡感:萬一new真的產生了異常怎麼辦?

一種解決方案:

#define NEW(PTR, TYPE)                       \
        try { (PTR) = new TYPE; }                \
        catch (std::bad_alloc&) { assert(0); }

說明:bad_alloc是operator new不能滿足內存分配請求時拋出的異常類型。assert是個宏。這個宏檢查傳給它的表達式是否非零,如果不是非零值,就會發出一條出錯信息並調用abort。assert 只是在沒定義標準宏NDEBUG的時候,即在調試狀態下才這麼做。

問題:1.具有宏的通病,2.沒有考慮到new有各種各樣的使用方式。比如:
 new T;
 new T(constructor arguments);
 new T[size];

另一種解決方案:


當內存分配請求不能滿足時,調用你預先指定的一個出錯處理函數。
這個方法基於一個常規,即當operator new不能滿足請求時,會在拋出異常之前調用客戶指定的一個出錯處理函數——一般稱爲new-handler函數。

 typedef void (*new_handler)();
 new_handler set_new_handler(new_handler p) throw();

    set_new_handler的輸入參數是operator new分配內存失敗時要調用的(新的)出錯處理函數的指針,返回值是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];
  ...
}

一個設計得好的new-handler函數必須實現下面功能中的一種:
  1. 產生更多的可用內存
  2. 安裝另一個不同的new-handler函數
  3. 卸除new-handler
  4.  拋出std::bad_alloc或從std::bad_alloc繼承的其他類型的異常
  5. 沒有返回

在類中使用:

 class X {
    public:
      static new_handler set_new_handler(new_handler p);
      static void * operator new(size_t size);
    private:
      static new_handler currentHandler;
 };
new_handler X::currentHandler;      // 缺省設置currentHandler爲0(即null)
new_handler X::set_new_handler(new_handler p)
{
  new_handler oldHandler = currentHandler;
  currentHandler = p;
  return oldHandler;
}


最後看看X的operator new所做的:



  1. 調用標準set_new_handler函數,輸入參數爲X的出錯處理函數。
  2. 調用全局operator new分配內存。
  3. 假設全局operator new爲類型X的對象分配內存成功,, X的operator new會再次調用標準set_new_handler 來恢復最初的全局出錯處理函數。
實現:
void * X::operator new(size_t size)
{
  new_handler globalHandler =                //  安裝X的new_handler
    std::set_new_handler(currentHandler);
  void *memory;
  try {                                      //  嘗試分配內存
    memory = ::operator new(size);
  }
  catch (std::bad_alloc&) {                  // 恢復舊的new_handler
    std::set_new_handler(globalHandler);
    throw;                                   // 拋出異常
  }
  std::set_new_handler(globalHandler);       // 恢復舊的new_handler
                                              
  return memory;
}

使用:
void noMoreMemory();                   // X的對象分配內存失敗時調用的
                                       // new_handler 函數的聲明
                                       //
X::set_new_handler(noMoreMemory);
                                       // 把noMoreMemory設置爲X的
                                       // new-handling函數
X *px1 = new X;                        // 如內存分配失敗,
                                       // 調用noMoreMemory
string *ps = new string;               // 如內存分配失敗,
                                       // 調用全局new-handling函數
                                       //
X::set_new_handler(0);                 // 設X的new-handling函數爲空
                                       //
X *px2 = new X;                        // 如內存分配失敗,立即拋出異常
                                       // (類X沒有new-handling函數)

使用模板類:

定義:
emplate<class T>    //  提供類set_new_handler支持的
class NewHandlerSupport {   // “混合風格”的基類
public:
  static new_handler set_new_handler(new_handler p);
  static void * operator new(size_t size);
private:
  static new_handler currentHandler;
};
template<class T>
new_handler NewHandlerSupport<T>::set_new_handler(new_handler p)
{
  new_handler oldHandler = currentHandler;
  currentHandler = p;
  return oldHandler;
}
template<class T>
void * NewHandlerSupport<T>::operator new(size_t size)
{
  new_handler globalHandler =
    std::set_new_handler(currentHandler);
  void *memory;
  try {
    memory = ::operator new(size);
  }
  catch (std::bad_alloc&) {
    std::set_new_handler(globalHandler);
    throw;
  }
  std::set_new_handler(globalHandler);
  return memory;
}
// this sets each currentHandler to 0
template<class T>
new_handler NewHandlerSupport<T>::currentHandler;

使用:有了這個模板類,對類X加上set_new_handler功能就很簡單了:只要讓X從newHandlerSupport<X>繼承:
// note inheritance from mixin base class template. (See
// my article on counting objects for information on why
// private inheritance might be preferable here.)
class X: public NewHandlerSupport<X> {
  ...                 // as before, but no declarations for
};                    // set_new_handler or operator new


    不管是用“正規”(即拋出異常)形式的new還是“無拋出”形式的new,重要的是你必須爲內存分配失敗做好準備。!






發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章