# Item 52: 如果编写了 placement new,就要编写 placement delete
作者:[Scott Meyers](http://aristeia.com/)
译者:[fatalerror99 (iTePub's Nirvana)](mailto:fatalerror9999@hotmail.com?subject=Item%2049)
发布:[http://blog.csdn.net/fatalerror99/](http://blog.csdn.net/fatalerror99/)
在 C++ 动物园中,placement new 和 placement delete 并不是最常遇到的野兽,所以如果你和它们不熟也不必担心。作为替代,回想一下 [Items 16](http://blog.csdn.net/fatalerror99/archive/2005/07/20/430119.aspx) 和 [17](http://blog.csdn.net/fatalerror99/archive/2005/07/21/431251.aspx),当你写下一个这样的 new 表达式,
```
Widget *pw = new Widget;
```
有两个函数会被调用:一个是 operator new 用于分配内存,第二个是 Widget 的 default constructor(缺省构造函数)。
假设第一个调用成功,而第二个调用导致抛出一个 exception(异常)。这种情况下,第 1 步中完成的内存分配必须被撤销。否则就是一个内存泄漏。客户代码不可能回收这些内存,因为,如果 Widget 的 constructor(构造函数)抛出一个 exception(异常),pw 根本就没有被赋值。对于客户来说无法得到指向应该被回收的内存的指针。所以撤销第 1 步的职责必然落在了 C++ runtime system(C++ 运行时系统)的身上。
runtime system(运行时系统)恰当地调用与它在第 1 步中调用的 operator new 的版本相对应的 operator delete,但是只有在它知道哪一个 operator delete——可能有许多——最恰当的时候它才能做到这一点。如果你正在摆弄具有常规的 signatures(识别特征)的 new 和 delete 版本,这不成问题,因为常规的 operator new,
```
void* operator new(std::size_t) throw(std::bad_alloc);
```
对应常规的 operator delete:
```
void operator delete(void *rawMemory) throw(); // normal signature
// at global scope
void operator delete(void *rawMemory, // typical normal
std::size_t size) throw(); // signature at class
// scope
```
当你只使用 new 和 delete 的常规形式时,runtime system(运行时系统)找出知道如何撤销 new 所做的事情的 delete 没什么麻烦。然而,当你开始声明 operator new 的非常规形式——带有额外参数的形式的时候,which-delete-goes-with-this-new(哪一个 delete 和这个 new 配对)的问题就出现了。
例如,假设你编写了一个 class-specific(类专用)的 operator new,它需要一个用于记录分配信息的 ostream 的规格描述,而你又编写了一个常规的 class-specific(类专用)的 operator delete:
```
class Widget {
public:
...
static void* operator new(std::size_t size, // non-normal
std::ostream& logStream) // form of new
throw(std::bad_alloc);
static void operator delete(void *pMemory // normal class-
std::size_t size) throw(); // specific form
// of delete
...
};
```
这个设计是成问题的,但是在我们探究为什么之前,我们需要做一个简要的术语说明。
当一个 operator new function 持有额外的参数(除了那个必要的 size_t 参数),这个 function 就被称为 new 的 _placement_ 版本。前面那个 operator new 就是这样一个 placement 版本。有一个特别有用的 placement new,它持有一个指针,这个指针指定了一个 object 被构造的位置。那个 operator new 如下:
```
void* operator new(std::size_t, **void *pMemory**) throw(); // "placement
// new"
```
new 的这个版本是 C++ 标准库的一部分,只要 #include <new> 你就可以访问它。需要指出,这个 new 用于 vector 内部,在 vector 的尚未使用的空间内创建 objects。它也是最初的 placement new。实际上,这就是这类函数被称为 _placement new_ 的来历。这就意味着术语 "placement new" 被赋予了更多的含义。大多数情况下,当人们谈到 placement new,他们谈的就是这个特定的函数,持有一个 void\* 类型的额外参数的 operator new。较少情况下,他们谈的是持有额外参数的 operator new 的任意版本。根据上下文通常可以搞清楚任何暧昧,重要的是要了解到通用术语 "placement new" 意味着持有额外参数的 new 的任意版本,因为短语 "placement delete"(过一会儿我们就会遇到它)直接起源于它。
我们让我们先返回到 Widget class 的 declaration(声明),就是我说设计成问题的那个。麻烦就在于这个 class 会引发微妙的 memory leaks(内存泄漏)。考虑如下客户代码,在动态创建一个 Widget 时,它将在 cerr 记录分配信息:
```
Widget *pw = new (std::cerr) Widget; // call operator new, passing cerr as
// the ostream; _this leaks memory_
// _if the Widget constructor throws_
```
重申一次,如果内存分配成功而 Widget constructor(构造函数)抛出一个 exception(异常),runtime system(运行时系统)有责任撤销 operator new 所执行的分配。然而,runtime system(运行时系统)不能真正了解被调用的 operator new 版本是如何工作的,所以它自己无法撤销那个分配。runtime system(运行时系统)转而寻找一个和 operator new 持有相同数量和类型额外参数的 operator delete 版本,而且,如果它找到了,它将调用它。在当前情况下,operator new 持有一个 ostream& 类型的额外参数,所以相应的 operator delete 应该具有这样的 signature(识别特征):
```
void operator delete(void *, **std::ostream&**) throw();
```
与 new 的 placement 版本类似,持有额外参数的 operator delete 版本被称为 _placement deletes_。当前情况下,Widget 没有声明 operator delete 的 placement 版本,所以 runtime system(运行时系统)不知道如何撤销所调用的 placement new 所做的事情。结果,它什么都不做。在本例中,如果 Widget constructor(构造函数)抛出一个 exception(异常),没有 _operator delete_ 可以被调用!
规则很简单:如果一个带有额外参数的 operator new 没有带有同样额外参数的 operator delete 相匹配,当一个由 new 生成的内存分配需要撤销的时候没有 operator delete 可以被调用。为了消除前面的代码中的 memory leak(内存泄漏),Widget 需要声明一个与 logging placement new 相对应的 placement 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();**
...
};
```
这样改变之后,如果从下面这个语句的 Widget constructor(构造函数)中抛出一个 exception(异常),
```
Widget *pw = new (std::cerr) Widget; // as before, but no leak this time
```
相应的 placement delete 自动被调用,而这就让 Widget 确保没有内存被泄漏。
然而,考虑以下情况会发生什么,如果没有抛出 exception(异常)(这是通常的情况)而我们的客户代码中又有一个 delete:
```
delete pw; // invokes the normal
// operator delete
```
就像注释中所说的,这样将调用常规 operator delete,而不是 placement 版本。只有在调用一个与 placement new 相关联的 constructor(构造函数)时发生一个 exception(异常),placement delete 才会被调用。将 delete 施加于一个指针(诸如上面的 pw)绝对不会引起一个 delete 的 placement 版本的调用。绝对不会。
这就意味着为了预防所有与 new 的 placement 版本相关的 memory leaks(内存泄漏),你必须既提供常规 operator delete(用于构造过程中没有抛出 exception(异常)时),又要提供一个持有与 operator new 相同的 extra arguments(额外参数)的 placement 版本(用于相反情况)。这样,你就再也不会因为微妙的 memory leaks(内存泄漏)而睡不着觉了。好吧,至少是不会因为这里这些微妙的 memory leaks(内存泄漏)。
顺便说一下,因为 member function(成员函数)的名字会覆盖外围的具有相同名字的函数(参见 [Item 33](http://blog.csdn.net/fatalerror99/archive/2005/09/13/479864.aspx)),你需要小心避免用 class-specific(类专用)的 news 覆盖你的客户所希望看到的其它 news(包括其常规版本)。例如,如果你有一个只声明了一个 operator new 的 placement 版本的 base class(基类),客户将发现 new 的常规形式对他们来说无法使用:
```
class Base {
public:
...
static void* operator new(std::size_t size, // this new hides
std::ostream& logStream) // the normal
throw(std::bad_alloc); // global forms
...
};
Base *pb = new Base; // error! the normal form of
// operator new is hidden
Base *pb = new (std::cerr) Base; // fine, calls Base's
// placement new
```
同样,derived classes(派生类)中的 operator news 覆盖 operator news 的全局和继承来的版本的 operator new:
```
class Derived: public Base { // inherits from Base above
public:
...
static void* operator new(std::size_t size) // redeclares the normal
throw(std::bad_alloc); // form of new
...
};
Derived *pd = new (std::clog) Derived; // error! Base's placement
// new is hidden
Derived *pd = new Derived; // fine, calls Derived's
// operator new
```
[Item 33](http://blog.csdn.net/fatalerror99/archive/2005/09/13/479864.aspx) 讨论了这种名字覆盖的需要考虑的细节,如果打算编写内存分配函数,你要记住,在缺省情况下,C++ 在全局范围提供如下形式的 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, // nothrow new —
const std::nothrow_t&) throw(); // see [Item 49](http://blog.csdn.net/fatalerror99/archive/2006/02/28/612673.aspx)
```
如果你在一个 class 中声明了任何 operator news,都将覆盖所有这些标准形式。除非你有意防止 class 的客户使用这些形式,否则,除了你创建的任何自定义 new 形式以外,还要确保它们都可以使用。当然,还要确保为每一个你使其可用的 operator new 提供相应的 operator delete。如果你要这些函数具有通常的行为,只需要让你的 class-specific(类专用)版本去调用 global(全局)版本即可。
达到这种效果的一个简单方法是创建一个包含 new 和 delete 的全部常规形式的 base class(基类):
```
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); }
};
```
想要在标准形式之外增加自定义形式的客户就能够使用 inheritance(继承)和 using declarations(使用声明)(参见 [Item 33](http://blog.csdn.net/fatalerror99/archive/2005/09/13/479864.aspx))来得到标准形式:
```
class Widget: public StandardNewDeleteForms { // inherit std forms
public:
using StandardNewDeleteForms::operator new; // make those
using StandardNewDeleteForms::operator delete; // forms visible
static void* operator new(std::size_t size, // add a custom
std::ostream& logStream) // placement new
throw(std::bad_alloc);
static void operator delete(void *pMemory, // add the corres-
std::ostream& logStream) // ponding place-
throw(); // ment delete
...
};
```
**Things to Remember**
* 在编写一个 operator new 的 placement 版本时,确保同时编写 operator delete 的相应的 placement 版本。否则,你的程序可能会发生微妙的,断续的 memory leaks(内存泄漏)。
* 当你声明 new 和 delete 的 placement 版本时,确保不会无意中覆盖这些函数的常规版本。
- Preface(前言)
- Introduction(导言)
- Terminology(术语)
- Item 1: 将 C++ 视为 federation of languages(语言联合体)
- Item 2: 用 consts, enums 和 inlines 取代 #defines
- Item 3: 只要可能就用 const
- Item 4: 确保 objects(对象)在使用前被初始化
- Item 5: 了解 C++ 为你偷偷地加上和调用了什么函数
- Item 6: 如果你不想使用 compiler-generated functions(编译器生成函数),就明确拒绝
- Item 7: 在 polymorphic base classes(多态基类)中将 destructors(析构函数)声明为 virtual(虚拟)
- Item 8: 防止因为 exceptions(异常)而离开 destructors(析构函数)
- Item 9: 绝不要在 construction(构造)或 destruction(析构)期间调用 virtual functions(虚拟函数)
- Item 10: 让 assignment operators(赋值运算符)返回一个 reference to *this(引向 *this 的引用)
- Item 11: 在 operator= 中处理 assignment to self(自赋值)
- Item 12: 拷贝一个对象的所有组成部分
- Item 13: 使用对象管理资源
- Item 14: 谨慎考虑资源管理类的拷贝行为
- Item 15: 在资源管理类中准备访问裸资源(raw resources)
- Item 16: 使用相同形式的 new 和 delete
- Item 17: 在一个独立的语句中将 new 出来的对象存入智能指针
- Item 18: 使接口易于正确使用,而难以错误使用
- Item 19: 视类设计为类型设计
- Item 20: 用 pass-by-reference-to-const(传引用给 const)取代 pass-by-value(传值)
- Item 21: 当你必须返回一个对象时不要试图返回一个引用
- Item 22: 将数据成员声明为 private
- Item 23: 用非成员非友元函数取代成员函数
- Item 24: 当类型转换应该用于所有参数时,声明为非成员函数
- Item 25: 考虑支持不抛异常的 swap
- Item 26: 只要有可能就推迟变量定义
- Item 27: 将强制转型减到最少
- Item 28: 避免返回对象内部构件的“句柄”
- Item 29: 争取异常安全(exception-safe)的代码
- Item 30: 理解 inline 化的介入和排除
- Item 31: 最小化文件之间的编译依赖
- Item 32: 确保 public inheritance 模拟 "is-a"
- Item 33: 避免覆盖(hiding)“通过继承得到的名字”
- Item 34: 区分 inheritance of interface(接口继承)和 inheritance of implementation(实现继承)
- Item 35: 考虑可选的 virtual functions(虚拟函数)的替代方法
- Item 36: 绝不要重定义一个 inherited non-virtual function(通过继承得到的非虚拟函数)
- Item 37: 绝不要重定义一个函数的 inherited default parameter value(通过继承得到的缺省参数值)
- Item 38: 通过 composition(复合)模拟 "has-a"(有一个)或 "is-implemented-in-terms-of"(是根据……实现的)
- Item 39: 谨慎使用 private inheritance(私有继承)
- Item 40: 谨慎使用 multiple inheritance(多继承)
- Item 41: 理解 implicit interfaces(隐式接口)和 compile-time polymorphism(编译期多态)
- Item 42: 理解 typename 的两个含义
- Item 43: 了解如何访问 templatized base classes(模板化基类)中的名字
- Item 44: 从 templates(模板)中分离出 parameter-independent(参数无关)的代码
- Item 45: 用 member function templates(成员函数模板) 接受 "all compatible types"(“所有兼容类型”)
- Item 46: 需要 type conversions(类型转换)时在 templates(模板)内定义 non-member functions(非成员函数)
- Item 47: 为类型信息使用 traits classes(特征类)
- Item 48: 感受 template metaprogramming(模板元编程)
- Item 49: 了解 new-handler 的行为
- Item 50: 领会何时替换 new 和 delete 才有意义
- Item 51: 编写 new 和 delete 时要遵守惯例
- Item 52: 如果编写了 placement new,就要编写 placement delete
- 附录 A. 超越 Effective C++
- 附录 B. 第二和第三版之间的 Item 映射