条款52:写了placement new也要写palcement delete
问题:
Widget* pw=new Widget;
这条语句的执行导致两个函数被使用:一个是用以分配内存的operator new,另一个是Widget的default构造函数。假设其中第一个函数调用成功,第二个函数抛出异常。这样第一步中operator new的内存分配所得必须取消并恢复旧观,否则会造成内存泄漏,这个时候,客户没有能力归还内存,因为如果Widget构造函数抛出异常,pw当前尚未被赋值,客户手上就没有相应的指针执行指向该被归还的内存,取消第一步并恢复旧观的重任就变成了C++运行期系统的重任。
运行期系统会调用第一步中operator new的相应operator delete版本,前提是它必须知道哪一个operator delete被调用:
//正常的operator new
void * operator new(std::size_t) throw(std::bad_alloc);
//正常的operator delete
//1、global作用域中的正常签名式
void operator delete(void* rawMemory) throw();
//2、class作用域中典型的签名式
void operator delete(void* rawMemory,std::size_t size) throw();
然而当我们声明一个非正常形式的operator new,也就是带有附加参数的operator new时,那么该调用那个operator delete呢?
class Widget{
public:
...
static void* operator new(std::size_t,std::ostream& logStream) throw(std::bad_alloc);//非正常形式new
static void operator delete(void* pMemory std::size_t size) throw()//正常的class专属delete
...
};
1、placement new/delete的提出
如果operator new接受的参数除了一定会有的那个size_t之外还有其他,这便是所谓的placement new,众多的placement new版本中有一个“接受一个指针指向对象该被构造之处”,这种placement new已经被纳入C++标准程序库,调用#include <new>就可以取用它。这种operator new的样子如下所示:
void* operator new(std::size_t,void* pMemory) throw();
上面我们针对Widget已经声明了一种placement new版本,如何使用它呢?下面举一个例子便于大家理解:
Widget* pw=new (std::cerr)Widget;//调用operator new并传递cerr作为其ostream实参,这个动作会在Widget构造函数抛出异常时泄漏内存
那么此时如果抛出异常的话怎样取消operator new的分配并恢复旧观,由于运行期间系统无法知道真正被调用的那个operator new如何运作,因此它无法取消分配并恢复旧观,取而代之得失C++运行期系统寻找“参数个数和类型都与operator new相同的”operator delete。如果找到,就是它的调用对象。那么Widget的这种形式的operator new对应的operator delete版本应该是:
void operator delete(void*,std::ostream&) throw();
这也就引出了placement delete的定义,如果operator delete如果接受额外参数,便称为placement delete。
下面我们写一个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) throw();
static void operator delete(void* pMemory,std::ostream& logStream) throw();
...
};
2、placement new的继承
class Base{
public:
...
static void* operator new(std::size_t size,std::ostream& logStream) throw(std::bad_alloc);//这个new会遮掩正常的global形式
...
};
Base* pb=new Base;//错误!因为正常形式的operator new被遮掩
Base* pb=new (std::cerr)Base;//正确,调用Base的placement new
class Derived:public Base{
public:
...
static void* operator new(std::size_t size);
...
};
Derived* pd=new (std::clog)Derived;//错误,因为Base中的placement new被遮掩了
Derived* pd=new Derived;//正确
要解决遮掩问题的话,我们需要家里一个Base class,内含所有正常形式的new和delete:
class StandardNewDeleteForms{
public:
static void* operator new(std::size_t size) throw(std::bad_alloc){
return ::operator new(size);
}
static void opearator 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);
}
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&nt) 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();
...
};
PS:
C++中operator new/delete或者operator new[]/delete[]的解析方式:http://blog.csdn.net/hazir/article/details/21413833
总结:
1)当你写了一个placement operator new,请确定也谢了对应的placement operator delete,如果没有这样做,程序可能会发生隐蔽而时断时续的内存泄漏;
2)当你声明了placement new和placement delete,请确定不要无意识地遮掩了它们的正常版本。