「placement new」一般是專指指定了位置的new(std::size_t size, void *mem)
,用於vector
申請capacity
剩餘的可用內存。 但廣義的」placement new」指的是擁有額外參數的operator new
。html
new
和delete
是要成對的,由於當構造函數拋出異常時用戶沒法獲得對象指針,於是delete
的責任在於C++運行時。 運行時須要找到匹配的delete
並進行調用。所以當咱們編寫了」placement new」時,也應當編寫對應的」placement delete」, 不然會引發內存泄露。在編寫自定義new
和delete
時,還要避免不當心隱藏它們的正常版本。函數
當構造函數拋出異常時,C++會調用與new
一樣簽名的delete
來撤銷new
。但若是咱們沒有聲明對應的delete
:spa
class Widget{ public: static void* operator new(std::size_t size, std::ostream& log) throw(std::bad_alloc); Widget(){ throw 1; } }; Widget *p = new(std::cerr) Widget;
構造函數拋出了異常,C++運行時嘗試調用delete(void *mem, std::ostream& log)
, 但Widget
沒有提供這樣的delete
,因而C++不會調用任何delete
,這將致使內存泄露。 因此在Widget
中須要聲明一樣簽名的delete
:指針
static void operator delete(void *mem, std::ostream& log);
但客戶還可能直接調用delete p
,這時C++運行時不會把它解釋爲」placement delete」,這樣的調用會使得Widget
拋出異常。 因此在Widget
中不只要聲明」placement delete」,還要聲明一個正常的delete
。code
class Widget{ public: static void* operator new(std::size_t size, std::ostream& log) throw(std::bad_alloc); static void operator delete(void *mem, std::ostream& log); static void operator delete(void *mem) throw(); Widget(){ throw 1; } };
這樣,不管是構造函數拋出異常,仍是用戶直接調用delete p
,內存都能正確地回收了。orm
在Item 33中提到,類中的名稱會隱藏外部的名稱,子類的名稱會隱藏父類的名稱。 因此當你聲明一個」placement new」時:htm
class Base{ public: static void* operator new(std::size_t size, std::ostream& log) throw(std::bad_alloc); }; Base *p = new Base; // Error! Base *p = new (std::cerr) Base; // OK
普通的new
將會拋出異常,由於」placement new」隱藏了外部的」normal new」。一樣地,當你繼承時:對象
class Derived: public Base{ public: static void* operator new(std::size_t size) throw(std::bad_alloc); }; Derived *p = new (std::clog) Derived; // Error! Derived *p = new Derived; // OK
這是由於子類中的」normal new」隱藏了父類中的」placement new」,雖然它們的函數簽名不一樣。 但Item 33中提到,按照C++的名稱隱藏規則會隱藏全部同名(name)的東西,和簽名無關。繼承
爲了不全局的」new」被隱藏,先來了解一下C++提供的三種全局」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(); // 見 Item 49
爲了不隱藏這些全局」new」,你在建立自定義的」new」時,也分別聲明這些簽名的」new」並調用全局的版本。 爲了方便,咱們能夠爲這些全局版本的調用聲明一個父類StandardNewDeleteForms
:
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); } };
而後在用戶類型Widget
中using StandardNewDeleteForms::new/delete
便可使得這些函數均可見:
class Widget: public StandardNewDeleteForms { // inherit std forms public: using StandardNewDeleteForms::operator new; using StandardNewDeleteForms::operator delete; static void* operator new(std::size_t size, std::ostream& log) throw(std::bad_alloc); // 自定義 placement new static void operator delete(void *pMemory, std::ostream& logStream) throw(); // 對應的 placement delete };