1. 程式人生 > 其它 >effective c++ 08 ——定製 new/delete

effective c++ 08 ——定製 new/delete

技術標籤:C++

東陽的學習筆記

  1. 多執行緒環境下的記憶體管理遭受單執行緒系統不曾有過的挑戰。
  2. STL所使用的heap記憶體是由容器所擁有的分配器物件(allocator objects)管理

條款49:瞭解new-handler的行為

  1. 當operator new 丟擲異常以反映一個未獲滿足的記憶體需求之前,它會先呼叫一個客戶指定的錯誤處理函式,一個所謂的 new-handler。為了指定這個 “用以處理記憶體不足的”函式,客戶必須呼叫 set_new_handler,其聲明於標頭檔案 。
namespace std {
   typedef void (*new_handler) (
); new_handler set_new_handler(new_handler p) throw(); }
  1. set_new_handler 的引數是個指標,指向 operator 無法分配足夠記憶體時該被呼叫的函式。其返回值也是個指標。
void outOfMem()
{
	std::cerr << "Unable to satisfy request for memory\n";
	std::abort();
}

int main()
{
	
	std::set_new_handler(outOfMem);  // 當 new 無法分配足夠記憶體時呼叫
int *pBigDataArray = new int[10000000000L]; std::cout << "i am alive" << std::endl; return 0; }
  1. 一個設計良好的 new-handler 必須做以下事情:
    i. 讓更多的記憶體可被使用。實現此策略的一個辦法是:程式一開始就分配一大塊記憶體(提前佔用),當new_handler 第一次被呼叫時,將他們釋放返還給程式使用。
    ii. 安裝另一個 new_handler。為達此目的,做法之一就是讓當前 new_handler 修改“會影響new_handler行為” 的static資料、namespace資料、global資料等。
    iii. 解除安裝 new_handler。也就是將 null 指標傳給 set_new_handle。一旦沒有安裝任何的 new_handler,下次呼叫就會丟擲異常。
    iv. 丟擲 bad_alloc,將異常傳播到記憶體索求處
    v. 不返回,通常呼叫abort() / exit()
  2. DO IT FOR ME:
class Widget: public NewHandlerSupport<Widget> { ... }
  1. 舊版本的C++,分配失敗就返回 null :(使用 std::nothrow 宣告使用返回null的版本)
Widget *pw2 = new ( std::nothrow ) Widget;  // 如果分配失敗,則返回0
if (pw2 == 0) ...   // 偵測 null
  1. Nothrow new 是一個頗為侷限的工具,因為它只適用於記憶體分配;後面的建構函式呼叫還是可能丟擲異常

條款50:瞭解 new / delete 的合理替換時機

  1. 什麼時候需要替換編譯器提供的 new / delete 呢?以下是三個常見的理由:
    i. 用來檢測運用上的錯誤。比如我們自行定義一個 new,以額外空間(位於客戶所得區塊之前或後)放置特定的 byte pattern (即簽名,signatures)。delete 通過檢查簽名是否原封不動來判斷是否發生了 overrununderrun,並可志記(log)下來。
    ii. 為了強化效能。編譯器自帶的 new / delete 採取 中庸之道。不對特定任何人有最佳表現。通過定製版本,是獲得最大效能提升的方法之一。
    iii. 為了收集使用上的統計資料。瞭解程式對記憶體的使用習慣。
  2. 編寫自己的 new / delete時注意齊位
  3. operator new 都應該包含一個迴圈,反覆呼叫某個 new_handling 函式
  4. 何時替換預設的 new / delete:
    i. 為了增加分配和歸還的速度
    ii. 為了降低預設記憶體管理器帶來的額外空間開銷
    iii. 為了彌補預設記憶體管理器中的非最佳齊位
    iv. 為了將相關物件成簇集中(在更少的記憶體頁上)
    v. 為了獲得非傳統的行為
    vi. 為了檢測運用錯誤
    vii. 為了收集使用統計資訊

條款51:編寫 new / delete時需固守常規

  1. new 不只一次嘗試分配記憶體,並在每次失敗後呼叫 new-handling函式
  2. operator new 應該包含一個無限迴圈,退出迴圈的唯一方法是:記憶體分配成功丟擲異常
  3. 如果你打算控制class專屬之 array 控制行為,那麼你需要實現 operator new []。記住:為一要做的事就是分配一塊未加工記憶體(row memory)。因為你無法知道每個物件有多大(在即成體系中, base / derived 的大小不一樣)
  4. delete 更簡單:你需要記住的唯一事情就是C++保證刪除 null 指標永遠安全。如果刪除的是個null指標,則什麼也不做
void operator delete(void *rawMemory) throw()
{
	if (rawMemory == 0) return;  // 如果刪除的是個null指標,則什麼也不做
}
  1. member版本的 delete,只需多一個動作檢查刪除數量。萬一你的 class 專屬的 operator new 將大小有誤的分配行為轉交給 ::operator new執行,你也必須將刪除行為轉交給 ::operator delete執行。

條款52:寫了placement new 也要寫 placement delete

Widget *pw = new Widget;
  1. 上述語句共呼叫了兩個函式:new 和 建構函式
  2. 假設第一個函式呼叫成功,第二個函式卻丟擲異常。這樣將會導致 pw 未被成功賦值,客戶手上也沒有能夠指標指向該被歸還的記憶體。取消步驟一併恢復舊觀的責任因此落到C++執行期系統上
  3. 執行期系統會高高興興地呼叫步驟一呼叫的 new 的相應 delete 版本。
void* operator new(std::size_t) throw(std::bad_alloc);

void operator delete(void* rawMemory) throw();//global 作用域中的正常簽名式

void operator delete(void* rawMemory, std::size_t size) throw();//class作用域中的典型簽名式。
  1. 當你自定義自己的 new 時, 也就是帶了附加引數的 new 時,究竟哪一個 delete 應該被呼叫就是一個問題了
class Widget{
public:
    static void* operator new(std::size_t size, std::ostream& logStream) throw(std::bad_alloc);
    static void* operator delete(void* pMemory, std::size_t size) throw();
};
  1. 如上例,一個有額外引數的 new 卻定義了一個 正常的 delete。此時C++執行期系統無法正常 delete,因此需要定義一個與自定義new對應的 placement delete。此時,對應的 placement delete會被呼叫,丟擲異常,但是,此時卻並不會洩漏任何記憶體
class Widget{
public:
    static void* operator new(std::size_t size, std::ostream& logStream) throw(std::bad_alloc);
    static void* operator delete(void* pMemory, std::size_t size) throw();
    static void* operator delete(void* pMemory, std::ostream& logStream) throw();
};
  1. placement delete只有在 “伴隨 placement new 呼叫而觸發的建構函式”出現異常時才會被呼叫,對一個指標施行 delete 絕對不對呼叫 placement delete
  2. 由於成員函式的名稱會 外圍作用域中的相同名稱,因此:
    i. 建立一個 base class 內含所有正常形式的 new / delete
    ii. 繼承上述類,並使用 using 宣告
class StandardNewDeleteForms{
public:
    //normal new/delete
    static void* operator new(std::size_t size)throw(std::bad_alloc)
    {return ::operator new(size);}
    static void operator delete(void* pMemory) throw()
    {::operator delete(pMemory);}
    //placement new/delete
    static void*  operator new(std::size_t size, void* ptr) throw()
    {return ::operator new(size, ptr);}
    static void operator delete(void* pMemory, void* ptr)throw()
    {return ::operator delete(pMemory, ptr);}
    //nothrow new/delete
    static void* operator new(std::size_t size, const std::nothrow_t& nt)throw()
    {return ::operator new(size, nt);}
    static void operator delete(void* pMemory, const std::nothrow_t&) throw()
    {::operator delete(pMemory);}
};

class Widget: public StandardNewDeleteForms{
public:
    using StandardNewDeleteForms::operator new;
    using StandardNewDeleteForms::operator delete;
    static void* operator new(std::size_t size, std::ostream& logStream)throw(std::bad_alloc);
    static void operator delete(void* pMemory, std::ostream& logStream) throw();
};
  1. 當你宣告 placement new / delete,請確定不要無意識(非故意)地遮掩了他們的正常版本