寫了placement new就要寫placement delete

「placement new」一般是專指指定了位置的new(std::size_t size, void *mem),用於vector申請capacity剩餘的可用內存。 但廣義的」placement new」指的是擁有額外參數的operator newhtml

newdelete是要成對的,由於當構造函數拋出異常時用戶沒法獲得對象指針,於是delete的責任在於C++運行時。 運行時須要找到匹配的delete並進行調用。所以當咱們編寫了」placement new」時,也應當編寫對應的」placement delete」, 不然會引發內存泄露。在編寫自定義newdelete時,還要避免不當心隱藏它們的正常版本。函數

成對的delete

當構造函數拋出異常時,C++會調用與new一樣簽名的delete來撤銷new。但若是咱們沒有聲明對應的deletespa

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」,還要聲明一個正常的deletecode

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

而後在用戶類型Widgetusing 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 };
相關文章
相關標籤/搜索