placement new和placement delete并非C++ 兽栏中最常见的动物,如果你不熟悉它们,不要感到挫折或忧虑。回忆条款16 和17,当你写一个new表达式像这样:
widget* pw=new widget;
共有两个函数被调用:一个是用以分配内存的operator new,一个是widget的default构造函数。
假设其中第一个函数调用成功,第二个函数却抛出异常。既然那样,步骤一的内存分配所得必须取消并恢复旧观,否则会造成 内存泄露(memory leak)。在这个时候,客户没有能力归还内存,因为如果widget构造函数抛出异常,pw尚未被赋值,客户手上也就没有指针指向该被归还的内存。取消步骤一并恢复旧观的责任因此落到C++运行期系统身上。
如果operator new 接受的参数除了一定会有的那个size_t之外还有其他,这便是个所谓的placement new。众多placement new版本中特别有用的一个死“接受一个指针指向对象该被构造之处”,那样的operator new长相如下:
void* operator new(std::size_t size,void* pMemory) throw(); //placement new
这个版本的new已被纳入C++标准程序库,你只要#include <new>就可以取用它。这个new的用途之一是负责在vector的未使用空间上创建对象。它同时也是最早的placement new版本。实际上它正是这个函数的命名根据:一个特定位置上的new。以上说明意味术语placement new有多重定义。当人们谈到placemnet new,大多数时候他们谈的是此一特定版本,也就是“唯一额外实参是个void*”,少数时候才是指接受任意额外实参之operator new。上下文语境往往也能够使意义不明确的话语清晰起来,但了解这一点相当重要:一般性术语“placement new”意味带任意额外参数的new,因为另一个术语“placement delete”直接派生自它。稍后我们即将遭遇后者。
规则很简单:如果一个带额外参数的operator new没有“带相同额外参数”的对应版operaotr delete,那么当new的内存分配动作需要取消并恢复旧观时就没有任何operator delete会被调用。因此,为了消弭稍早代码中的内存泄露,类中有必要声明一个与placement new对应版本的placement delete.placement只有在“伴随placement new调用而触发的构造函数”出现异常是才会被调用。
附带一提,由于成员函数的名称会掩盖其外围作用域中的相同名称,你必须小心避免让class 专属的new掩盖客户期望的其他news(包括正常版本)。如果你在class内声明任何operator news,它会遮掩全局的标准new形式。除非你的意思就是要阻止class的客户使用这些形式,否则请确保它们在你所生成的任何定制型operator new之外还可用。对于每一个可用的operator new也请确定对应的operator delete.如果你希望这些函数有着平常的行为,只要你的class 专属版本调用global版本即可。
完成以上所言的一个简单做法是,建立一个base class,内含所有正常形式的new和delete:
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()
{
::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);
}
};
凡是想以自定形式扩充标准形式的客户,可利用继承机制及using声明式(见条款33)取得标准形式:
class widget:public StandardNewDeleteForms //继承标准形式
{
public:
using StandardNewDeleteForms::operator new; //让这些形式可见
using StandardNewDeleteForms::operator delete;
//添加一个自定的placement new
static void* operator new(std::size_t size,std::ostream& logSteam) throw(std::bad_alloc);
//添加一个对应的placement delete
static void operator delete(void* pMemory,std::ostream& logStream) throw();
};
请记住:
当你写一个placement operator new,请确定也写出了对应的placement operator delete。如果没有这样做,你的程序可能会发生隐微而时断时续的内存泄露。
当你声明placement new和placement delete,请确定不要无意识(非故意)地遮掩了它们的正常版本。