写了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 };
相关文章
相关标签/搜索