Effective C++第八章-new和delete

瞭解new-handle的行爲

當operator new拋出異常以反映一個未獲滿足的內存需求之前,它會先調用一個客戶指定的錯誤處理函數,一個所謂的new_handle。(new真正做的事情稍微更復雜些)爲了指定該函數,客戶必須要調用set_new_handler,那是聲明於< new >的一個標準程序庫函數:

namespace std{
    typedef void(* new_handler)();//new_handler是一個typedef,定義一個指針指向函數,該函數沒有參數也不返回任何東西。
    new_handler set_new_handler(new_handler p) throw();//set_new_handler則是獲得一個new_handler並返回一個new_handler的函數。參數指針指向operator new 無法分配足夠內存時該被調用的函數,返回的指針指向set_new_handler被調用前正在執行的那個new_handler函數。
}
void MyOutOfMemory()
{
    cout << "Out of memory error!" << endl;
    abort();
}
int main()
{
    set_new_handler(MyOutOfMemory);
    int *verybigmemory = new int[0x1fffffff];
    ...
}

定義於< new> 中的nothrow常量–該常量用來作爲operator new和operator new[]的參數,說明這兩個函數分配內存失敗時不拋出異常,而是返回一個null指針。

class widget{...};
widget* pw1 = new widget;//如果分配失敗,拋出bad_alloc
if(pw1 == 0 )..//該測試一定失敗
widget* pw2 = new (std::nothrow) widget;//如果分配失敗,返回0
if(pw2 == 0 )...//該測試可能成功

PS:< new >頭文件有:

  • Functions

    • operator new

    • operator new[]

    • operator delete

    • operator delete[]

    • set_new_handler

    • get_new_handler

  • Types(類型)

    • nothrow_t

    • new_handler

    • bad_alloc

    • bad_array_new_length

  • Constants(常量)

    • nothrow

編譯器自帶的new和delete PK 自制版本的new和delete

自制版本的new和delete的好處:

  1. 用來檢測運用上的錯誤
  2. 效能勝過缺省版本
  3. 自制版本的new和delete可輕鬆收集下列信息:在定製性new和定製性delete之前,理當先收集你的軟件如何使用其動態內存,分配區塊的大小分佈如何?壽命分佈如何?傾向於FIFO(先進先出)還是LIFO次序或隨機次序來分配和歸還?運用型態是否隨時間改變,也就是在不同的執行階段有不同的分配/歸還型態嗎?任何時刻所使用的最大動態分配量是多少?

齊位(alignment)

許多計算機體系結構要求特定的類型必須放在特定的內存地址上,例如它可能會要求指針的地址必須是4倍數或doubles的地址必須是8倍數。如果沒有奉行這個約束條件,可能導致運行期硬件異常。

C++要求所有operator new返回的指針都有適當的對齊(取決於數據類型)。

編寫new和delete需要遵守的規則

  • new

    operator new 的返回值,如果有能力供應客戶申請的內存,就返回一個指針指向那塊內存。如果沒有那個能力,就調用new_handle函數,該函數也許能做某些動作將某些內存釋放出來,只有當指向該函數的指針是null,operator new纔會拋出一個bad_alloc異常。

    operator new成員函數會被derived class繼承,但是,寫定製型內存管理器的一個最常見理由是爲針對某特定class對象分配行爲提供最優化,卻不是爲該class的任何derived class。也就是說,針對class X設計的operator new,其行爲很典型地只爲大小剛好爲sizeof(X)而設計。處理該問題的最佳做法就是:改採用標準operator new,例如

    void* Base::operator new(std::size_t size) throw(std::bad_alloc)
    {
    if(size != sizeof(Base))
        return ::operator new(size);//採用標準operator new
    ...
    }
  • delete

    • 保證”刪除null指針”永遠安全
    • 如果class專屬的operator new將大小有誤的分配行爲轉交::operator new 執行,則必須將大小有誤的刪除行爲轉交::operator delete執行。
    void* Base::operator delete(void* rawmemory,std::size_t size) throw()
    {
    if(rawmemory== 0 ) return;//保證"刪除null指針"永遠安全
    if(size != sizeof(Base)){
        ::operator delete(rawmemory);//採用標準operator delete
        return;
    }
    歸還rawmemory所指的內存
    return;
    }

placement new和placement delete

C++在全局作用域內提供以下三種operator new:

throwing void* operator new (std::size_t size);
nothrow void* operator new (std::size_t size, const std::nothrow_t& nothrow_value) noexcept;
placement void* operator new (std::size_t size, void* ptr) noexcept;

和以下三種operator delete:

ordinary void operator delete (void* ptr) noexcept;
nothrow void operator delete (void* ptr, const std::nothrow_t& nothrow_constant) noexcept;
placement void operator delete (void* ptr, void* voidptr2) noexcept;

PS:

  1. C++11中的noexcept大致等同於C++98版的throw();都表示函數不拋出任何異常。
  2. operator new詳細介紹
  3. operator delete詳細介紹

widget* pw = new widget時,共有兩個函數被調用:一個是用以分配內存的operator new,一個是widget的default構造函數。

假設第一個函數調用成功,第二個函數拋出異常,則運行期系統有責任尋找並調用operator new的相應operator delete版本。

需注意的是,如果找不到與之對應的delete,運行期系統會什麼都不做,導致內存泄漏。

因此,若定製new和delete請成對,一個帶額外參數的operator new最好有帶相同額外參數的對應版operator delete。例如:

class widget{
public:
    //placement new 
    static void* operator new(std::size_t size, std::ostream& logstream) throw(std::bad_alloc);
    //ordinary delete
    static void* operator delete(void* pmemory) throw();
    //placement delete
    static void* operator delete(void* pmemory,std::ostream& logstream) throw();
};

對所有與placement new相關的內存泄漏,我們必須同時提供一個正常的operator delete(用於構造期間無任何異常被拋出)和一個帶相同額外參數的placement版本(用於構造期間有異常被拋出)。

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