条款52:写了placement new也要写placement delete
Write placement delete if you write placement new.
我们都知道当你在写一个new表达式像这样:
Widget* new_widget = new Widget;
共有两个函数被调用:一个是用以分配内存的operator new,一个是Widget的default构造函数.
那么假设我们现在遇到的情况是:第一个函数调用成功,第二个函数却抛出异常.按照常理,在步骤一中所分配的
内存必须取消,否则就会造成内存泄露.而在这个时候客户已经没有能力归还内存了,因为手上没有指向这块内存的
指针,故此任务就落到了C++运行期系统身上.
为了完成任务,运行期系统当然就会调用步骤一所用的operator new的相应operator delete版本.如果当前要处
理的是拥有正常签名的new和delete版本,这好办!因为正常的operator new签名式:
void* operator new(std::size_t) throw (std::bad_alloc);
对应的正常的operator delete签名式:
void operator delete(void* raw_memory)throw();//global 作用域中的正常签名式
void operator delete(void* raw_memory,std::size_t size)throw();//class作用域中典型的签名式
因此,当你只使用正常形式的new和delete,运行期系统毫无问题可以找出那个'知道如何取消new所作所为并恢
复旧观'的delete.然而当你开始声明非正常形式的operator new,即就是附加参数的operator new,问题就出来了.
为了说明这个问题,我们依然用Widget例子,假设你写了一个class专属的operator new,要求接受一个ostream,
用来logged相关分配信息,同时又写了一个正常形式的class专属operator delete:
struct Widget{
//非正常形式的new
static void* operator new(std::size_t size,std::ostream& log_stream)throw(std::bad_alloc);
//正常class专属delete.
static void operator delete(void* memory, std::size_t size)throw();
...
};
在这里我们定义:如果operator new接受的参数除了一定会有的那个size_t之外还有其它,这个便是所谓的
placement new.而众多的placement new版本中特别提到的是'接受一个指针指向对象该被构造之处',那样的
operator new长相如下:
void* operator new( std::size_t, void* memory ) throw();//placement new
该版本的new已经被纳入C++标准程序库,你只要#include <new>就可以取用它,它的用处就是负责在vector的未
使用空间上创建对象.
现在让我们回到Widget的声明式,这个Widget将引起微妙的内存泄漏.考虑下面的测试代码,它将在动态创建一个
Widget时将相关的分配信息志记与cerr:
Widget* new_widget = new( std::cerr ) Widget;
在说一下我们先前提到的问题,如果内存分配成功,而构造抛出异常,运行期就有责任取消operator new的分配并
恢复旧观.然而运行期系统无法知道真正被调用的那个operator new如何运作,因此它无法取消分配并恢复旧观,所以
上述做法行不通.取而代之的是,运行期系统寻找'参数个数和类型都与operator new相同'的某个operator
delete.如果找打,那就是它该调用的对象.既然这里的operator new接受的类型为ostream&的额外实参,所以对应的
operator delete就应该是:
void operator delete(void*,std::ostream&)throw();
类似于new的placement版本,operator delete如果接受额外参数,便称为placement deletes.现在,既然Widget
没有声明placement版本的operator delete,所以运行期系统不知道如何取消并恢复原先对placement new的调用.于
是什么也不做.本例之中如果构造抛出异常,不会有任何operator delete被调用.
为了消除Widget中的内存泄漏,我们来声明一个palcement delete,对应与那个有志记功能的placement new:
struct Widget{
static void* operator new(std::size_t size, std::ostream& log_stream)throw(std::bad_alloc);
static void operator delete(void* memory) throw();
static void operator delete(void* memory,std::ostream& log_stream)throw();
...
};
这样改变之后,如果以下语句引发Widget构造函数抛出异常:
Widget* new_widget = new (std::cerr) Widget; //一如既往,但这次就不在发生泄漏.
然而如果没有抛出异常(大部分是这样的),客户代码中有个对应的delete,会发生什么事情:
delete pw; //call normal operator delete
调用的是正常形式的operator delete,而非其placement版本.请记住:placement delete只有在'伴随placement
new调用而触发的构造函数'出现异常时才会被调用.对着一个指针施行delete绝不会导致调用placement delete.
还有一点你需要注意的是:由于成员函数的名称会遮盖其外围作用域中的相同名称,你必须小心避免让class专属
news遮盖客户期望的其它news(包括正常版本).默认情况下,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.see Item 49.
如果你在class内声明任何operator news,它会遮掩上述这些标准形式.除非你的意思就是要阻止class的客户使
用这些形式,否则请确保它们在你所生成的任何定制型operator new之外还可用.对于每一个可用的operator new也
请确定提供对应的operator delete.如果希望这些函数都有着平常的行为,只要令你的class专属版本调用global版
本即可.
为了完成以上所言的一个简单做法就是建立一个base class,内含所有正常形式的new和delete:
struct StandardNewDeleteForms{
//normal new/delete
static void* operator new(std::size_t size)throw(std::bad_alloc)
{ return ::operator new( size ); }
static void operator delete(void* memory) throw()
{ ::operator delete( memory ); }
//placement new/delete
static void* operator new(std::size_t size,void* pointer)throw()
{ return ::operator new( size, pointer );}
static void operator delete(void* memory,void* pointer)throw()
{ return ::operator delete( memory, pointer ); }
//nothrow new/delete
static void* operator new(std::size_t size,const std::nothrow_t& no_throw)throw()
{ return ::operator new( size, no_throw ); }
static void operator delete(void* memory,const std::nothrow_t&)throw()
{ ::operator delete( memory ); }
};
凡是想自定形式扩充标准形式的客户,可利用继承机制及using声明式(Item 33)取得标准形式:
struct Widget:public StandardNewDeleteForms{
using StandardNewDeleteForms::operator new;
using StandardNewDeleteForms::operator delete;
static void* operator new(std::size_t size, std::ostream& log_stream) throw(std::bad_alloc);
static void operator delete(void* memory,std::ostream& log_stream) throw();
...
};
好了,本款讨论结束.
请记住:
■ 当你写一个placement operator new,请确定也写出了对应的placement operator delete.如果没有这样做,
你的程序可能会发生隐微而时断时续的内存泄漏.
■ 当你声明placement new和placement delete,请确定不要无意识地遮掩它们的正常版本.
来源:CSDN
作者:scofieldzhu
链接:https://blog.csdn.net/scofieldzhu/article/details/4737353