艺术编程

死不惊人死不休

翻译 [翻译] Effective C++, 3rd Edition, Item 52: 如果编写了 placement new,就要编写 placement delete收藏

Item 52: 如果编写了 placement new,就要编写 placement delete

作者:Scott Meyers

译者:fatalerror99 (iTePub's Nirvana)

发布:http://blog.csdn.net/fatalerror99/

在 C++ 动物园中,placement new 和 placement delete 并不是最常遇到的野兽,所以如果你和它们不熟也不必担心。作为替代,回想一下 Items 1617,当你写下一个这样的 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(识别特征)的 newdelete 版本,这不成问题,因为常规的 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

当你只使用 newdelete 的常规形式时,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 就被称为 newplacement 版本。前面那个 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),你需要小心避免用 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 讨论了这种名字覆盖的需要考虑的细节,如果打算编写内存分配函数,你要记住,在缺省情况下,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

如果你在一个 class 中声明了任何 operator news,都将覆盖所有这些标准形式。除非你有意防止 class 的客户使用这些形式,否则,除了你创建的任何自定义 new 形式以外,还要确保它们都可以使用。当然,还要确保为每一个你使其可用的 operator new 提供相应的 operator delete。如果你要这些函数具有通常的行为,只需要让你的 class-specific(类专用)版本去调用 global(全局)版本即可。

达到这种效果的一个简单方法是创建一个包含 newdelete 的全部常规形式的 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)来得到标准形式:

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(内存泄漏)。
  • 当你声明 newdelete 的 placement 版本时,确保不会无意中覆盖这些函数的常规版本。

发表于 @ 2007年01月21日 22:18:00|评论(loading...)

新一篇: [原创] 准备翻译 Boost 文档 | 旧一篇: [翻译] Effective C++, 3rd Edition, Item 51: 编写 new 和 delete 时要遵守惯例

用户操作
[即时聊天] [发私信] [加为好友]
fatalerror
订阅我的博客
XML聚合  FeedSky
订阅到鲜果
订阅到Google
订阅到抓虾
fatalerror的公告
Copyleft © 2005 - 2009 by fatalerror99 (iTePub's Nirvana)

本 BLog 所有文章除注明转载者外,均为本人原创或翻译,欢迎转载。转载时请保持文章完整并注明出处。

强烈推荐使用 Mozilla Firefox 浏览本 BLog。

MSN: fatalerror9999@hotmail.com

e-mail: fatalerror99@gmail.com

文章分类
收藏
    My baby
    我家未未(RSS)
    Our Projects
    boost 文档汉化 boost_doc_translation(RSS)
    不服不行
    Bjarne Stroustrup
    Alexander A. Stepanov
    Andrei Alexandrescu
    Bruce Eckel
    Charles Petzold
    Chris Sells
    David R. Musser
    Dennis M. Ritchie
    Donald E. Knuth
    Herb Sutter
    James Gosling
    Nicolai M. Josuttis
    Scott Meyers
    Stanley B. Lippman
    侯捷
    荣耀
    精点 BLog
    水瓶水蓝
    水瓶水蓝 —— 晃荡在阴阳两界的魂儿
    (RSS)
    CityLife 的流水账(RSS)
    为艺术而技术(RSS)
    乱发当风(RSS)
    微起涟漪 —— basse(RSS)
    暗金色月亮的赫拉迪克宝盒(RSS)
    杏坛雨的博客(RSS)
    王晓渔:书中自有……(RSS)
    开发 BLog
    C++ 的罗浮宫(RSS)
    Coofucoo's Blog--The Unadulterated Coofucoo(RSS)
    GreenCode's Blog(RSS)
    ilovevc 的专栏(RSS)
    lxwde 的专栏(RSS)
    oiramario(RSS)
    Ralph Zhang --- 在这里我只谈技术(RSS)
    ralph623 的专栏(RSS)
    renco 的专栏(RSS)
    Scorpio Auding @ Blog++(RSS)
    SnowFalcon 的专栏(RSS)
    Stan Lippman's BLog(RSS)
    Sutter's (Online) Mill(RSS)
    切尔斯基(RSS)
    刘未鹏 Mind Hacks
    周星星 之 Blog(RSS)
    孟岩(RSS)
    开心就好的代码人生(RSS)
    心如止水 —— coofucoo 的专栏(RSS)
    方舟(RSS)
    歌谣在风中飘舞(RSS)
    相信开源的力量(RSS)
    空谷幽兰,心如皓月 —— 陈皓专栏(RSS)
    艺术编程(RSS)
    透明思考 - 1(RSS)
    透明思考 - 2(RSS)
    陈硕的 Blog(RSS)
    开发网站
    CSDN.NET
    artima devdloper: Best practices in enterprise software development
    Experts Exchange
    IBM DeveloperWorks
    IBM DeveloperWorks 中国(RSS)
    Programmers' Heaven
    The Artima Developer Community
    The Code Project
    卡卡社区
    开发语言与环境
    (CHEZ (CHEZ SCHEME))
    .NET Languages
    PHP: Hypertext Preprocessor
    Eclipse.org home
    Python Programming Language
    REBOL Technologies
    ActiveState
    D Programming Language
    Eclipse Plugins
    Eclipse Plusin Central
    Eiffel Software
    GCL - GNU Common Lisp
    GNU Compiler Collection (GCC)
    Groovy
    IronPython
    NetBeans IDE
    Perl
    Ruby on Rails
    Ruby Programming Language
    The Programming Language Lua
    坛子若干
    iTePub
    自由小店 —— iTePub 共建共享电子图书交互平台
    (RSS)
    ChinaJavaWorld.com 技术论坛
    ChinaUnix
    CSDN 技术社区 —— 这个不说大家也知道
    Huihoo - Open Source Community
    ITPUB 论坛
    卡卡社区
    网络书店
    Amazon.com
    China-Pub 网上书店
    joyo Amazon 卓越亚马逊
    第二书店
    有一杯咖啡叫做 Java
    Hibernate
    Java Technology
    JavaWorld
    jGuru
    Spring Framework
    The Apache Software Foundation
    TheServerSide.COM: Your Enterprise Java Community
    有一部经典叫做 C++
    Boost C++ Libraries
    C Programming and C++ Programming
    C/C++ Reference
    cplusplus.com
    Programming in C++, Rules and Recommendations
    The ADAPTIVE Communication Enviroment (ACE)
    The C Standards Committee (ISO C)
    The C++ Standard Committee (ISO C++)
    有一只企鹅叫做 Linux
    Debian
    Fedora Project
    Linux Journal
    Linux Online!
    Linux 伊甸园
    Linux.com
    Red Hat
    SUSE Linux Enterprise from Novell
    The Linux Foundation
    The Linux Kernel Archives
    Ubuntu
    有一种自由叫做开源
    OpenBSD
    CodeGuru
    FreeBSD
    FSF - The Free Software Foundation
    GNU
    Huihoo - Open Source Community
    Open Source Initiative (OSI)
    OpenSolaris
    SourceForge.net
    The Open Enterprise Foundation (OEF)
    专业出版机构
    Addison-Wesley
    APress
    Manning Publications Co.
    McGraw-Hill
    O'Reilly
    Prentice Hall PTR
    Wiley
    Wordware Publishing, Inc.
    Wrox
    存档
    软件项目交易
    Csdn Blog version 3.1a
    Copyright © fatalerror