多应用+插件架构,代码干净,二开方便,首家独创一键云编译技术,文档视频完善,免费商用码云13.8K 广告
# 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 版本时,确保不会无意中覆盖这些函数的常规版本。