程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> Effective C++ 條款 52:寫了placement new也要寫placement delete

Effective C++ 條款 52:寫了placement new也要寫placement delete

編輯:C++入門知識

Effective C++ 條款 52:寫了placement new也要寫placement delete


(一)

當我們寫下了下面這個語句:

Widget* pw = new Widget;

這個時候,共有兩個函數被調用:一個分配內存的operator new,另外一個是Widget的default構造函數。

假設第一個調用成功,第二個卻拋出異常。步驟一所分配內存必須取消並恢復舊觀,否則會造成內存洩漏。這時,客戶沒能力歸還內存,因為Widget構造函數拋出異常,pw尚未被賦值,客戶手上也就沒有指針指向該被歸還的內存。

這個時候,取消步驟一,並恢復舊觀的責任就落到C++運行系統身上。

運行期系統會高興的調用步驟一所調用operator new的相應的operator delete版本,前提是他必須知道哪一個operator delete被調用(可能有許多個)。

如果目前面對的是擁有正常簽名式的new和delete,並不是問題,

正常的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作用域中的典型簽名式。


(二)

但是當你開始聲明非正常形式的operator new,也就是帶附加參數的operator new。

這個時候,“究竟哪一個delete伴隨這個new”的問題就浮現了:

比如說:

class專屬的operator new要求接受一個ostream,志記相關分配信息,同時又寫了個正常形式的class專屬operator 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(); 
};

如果operator new接受的參數除了一定會有的那個size_t之外還有其他,這個便是所謂的placement new。

眾多placement new中特別有一個是“接受一個指針指向對象該被構造之處”,那樣的operator new長相如下:

void* operator new(std::size_t, void* pMemory) throw();

Widget* pw = new(std::cerr) Widget;

這個如果Widget構造函數拋出異常,運行期系統無法知道真正被調用的那個new如何運作,因此無法取消分配並恢復舊觀。運行期系統尋找“參數個數和類型都與operator new相同的”某個operator delete。應該是:

void operator delete(void*, std::ostream&) throw();

被稱為placement deletes。

現在,既然Widget沒有聲明placement版本的operator delete,所以運行期系統什麼也不做。如果Widget構造函數拋出異常,不會有任何operator delete被調用。

所以會造成內存洩漏。

(三)

規則很簡單:如果一個帶額外參數的operator new沒有“帶相同額外參數”的對應版operator delete,那麼當new的內存分配動作需要取消並恢復時就沒有任何operator delete會被調用。所以有必要聲明一個placement delete,對應於那個有志記功能的placement new:

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(); 
};

這樣改變後,如果Widget構造函數拋出異常:

Widget* pw = new (std::cerr) Widget;

對應的placement delete會被自動調用,讓Widget有機會確保不洩露內存。

這個語句:

delete pw;//調用正常的operator delete

placement delete只有在“伴隨placement new調用而觸發的構造函數”出現異常時才會被調用。對一個指針施行delete絕不會導致調用placement delete。

這意味對所有placement new我們必須同時提供一個正常的delete和一個placement版本。

(四)

由於成員函數的名稱會掩蓋其外圍作用域中的相同名稱,你必須小心讓class專屬的news掩蓋客戶期望的其他news(包括正常版本)。

例如你有一個base class,其中聲明唯一一個placement operator new,客戶會發現他們無法使用正常形式的new:

class Base {
public:
	...
	static void* operator new(size_t size, ostream& logStream) throw(bad_alloc);  //這個new會遮掩正常的global形式
};

Base* pb = new Base;               //錯誤!因為正常形式的operator new被遮掩
Base* pb = new (std::cerr) Base;   //正確,調用Base的placement new.

同樣的道理,derived classes中的operator news會遮掩global版本和繼承而得的operator new版本:

class Derived : public Base {     //繼承先前的Base
public:
	...
	static void* operator new(std::size_t size) throw(std::bad_alloc);   重新聲明正常形式的new
	...
};

Derived* pd = new(std::clog) Derived;   //錯誤,因為Base的placement被遮掩了。
Derived* pd = new Derived;    //沒問題,調用Derived的operator new.


(五)

缺省情況下c++在global作用域內提供以下形式的operator new:

void* operator new(std::size_t) throw(std::bad_alloc);            //normal new 
void* operator new(std::size_t, void*) throw();                        //placement new 
void* operator new(std::size_t, const std::nothrow_t&) throw();// nothrow new

如果你在class內聲明任何operator news,它會遮掩上述這些標准形式。除非你的意思就是要阻止class的客戶使用這些形式,否則確保他們在你生成的任何定制型operator new之外還可用。

解決所有問題的一個簡單的做法是,建立一個base class,內含所有正常形式的new和delete:

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);} 
};

凡是想自定義形式擴充標准形式的客戶,可利用繼承機制及using聲明式取得標准形式:

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 operator new,請確定也寫出了對應的placement operator delete.如果沒有這樣做。你的程序可能會發生隱微而時斷時續的內存洩漏.
(2)當你聲明placement new和placement delete。請確定不要無意識地遮掩它們的正常版本.

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