“placement new”通常是专指指定了位置的new(std::size_t size, void *mem),用于vector申请capacity剩余的可用内存。 但广义的”placement new”指的是拥有额外参数的operator new

newdelete是要成对的,因为当构造函数抛出异常时用户无法得到对象指针,因而delete的责任在于C++运行时。 运行时需要找到匹配的delete并进行调用。因此当我们编写了”placement new”时,也应当编写对应的”placement delete”, 否则会引起内存泄露。在编写自定义newdelete时,还要避免不小心隐藏它们的正常版本。

成对的delete

当构造函数抛出异常时,C++会调用与new同样签名的delete来撤销new。但如果我们没有声明对应的delete

class Widget{
public:
static void* operator new(std::size_t size, std::ostream& log) throw(std::bad_alloc);
Widget(){ throw 1; }
}; Widget *p = new(std::cerr) Widget;

构造函数抛出了异常,C++运行时尝试调用delete(void *mem, std::ostream& log), 但Widget没有提供这样的delete,于是C++不会调用任何delete,这将导致内存泄露。 所以在Widget中需要声明同样签名的delete

static void operator delete(void *mem, std::ostream& log);

但客户还可能直接调用delete p,这时C++运行时不会把它解释为”placement delete”,这样的调用会使得Widget抛出异常。 所以在Widget中不仅要声明”placement delete”,还要声明一个正常的delete

class Widget{
public:
static void* operator new(std::size_t size, std::ostream& log) throw(std::bad_alloc);
static void operator delete(void *mem, std::ostream& log);
static void operator delete(void *mem) throw();
Widget(){ throw 1; }
};

这样,无论是构造函数抛出异常,还是用户直接调用delete p,内存都能正确地回收了。

名称隐藏

Item 33中提到,类中的名称会隐藏外部的名称,子类的名称会隐藏父类的名称。 所以当你声明一个”placement new”时:

class Base{
public:
static void* operator new(std::size_t size, std::ostream& log) throw(std::bad_alloc);
};
Base *p = new Base; // Error!
Base *p = new (std::cerr) Base; // OK

普通的new将会抛出异常,因为”placement new”隐藏了外部的”normal new”。同样地,当你继承时:

class Derived: public Base{
public:
static void* operator new(std::size_t size) throw(std::bad_alloc);
};
Derived *p = new (std::clog) Derived; // Error!
Derived *p = new Derived; // OK

这是因为子类中的”normal new”隐藏了父类中的”placement new”,虽然它们的函数签名不同。 但Item 33中提到,按照C++的名称隐藏规则会隐藏所有同名(name)的东西,和签名无关。

最佳实践

为了避免全局的”new”被隐藏,先来了解一下C++提供的三种全局”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(); // 见 Item 49

为了避免隐藏这些全局”new”,你在创建自定义的”new”时,也分别声明这些签名的”new”并调用全局的版本。 为了方便,我们可以为这些全局版本的调用声明一个父类StandardNewDeleteForms

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); }
};

然后在用户类型Widgetusing StandardNewDeleteForms::new/delete即可使得这些函数都可见:

class Widget: public StandardNewDeleteForms {           // inherit std forms
public:
using StandardNewDeleteForms::operator new;
using StandardNewDeleteForms::operator delete; static void* operator new(std::size_t size, std::ostream& log) throw(std::bad_alloc); // 自定义 placement new
static void operator delete(void *pMemory, std::ostream& logStream) throw(); // 对应的 placement delete
};

写了placement new就要写placement delete的更多相关文章

  1. 条款十: 如果写了operator new就要同时写operator delete

    为什么有必要写自己的operator new和operator delete? 答案通常是:为了效率.缺省的operator new和operator delete具有非常好的通用性,它的这种灵活性也 ...

  2. Hibernate写hql语句与不写hql语句的区别?

    写hql语句与不写hql语句的区别? 写hql语句:书写HQL语句,所有的查询与投影的设计均使用HQL语句完成. 不写hql语句:没有任何查询语句,所有的查询与投影的设计使用面向对象格式完成. 二者选 ...

  3. [改善Java代码]覆写equals方法必须覆写hashCode方法

    覆写equals方法必须覆写hashCode方法,这条规则基本上每个Javaer都知道,这也是JDK API上反复说明的,不过为什么要这样做呢?这两个方法之间有什么关系呢?本建议就来解释该问题,我们先 ...

  4. 写文件前, 检查目录写权限(PHP)

    写文件前, 检查目录写权限 写或保存文件前, 确保目录是可写的, 假如不可写, 输出错误信息. 这会节约你很多调试时间. linux系统中, 需要处理权限, 目录权限不当会导致很多很多的问题, 文件也 ...

  5. 为什么覆写equals必须要覆写hashCode?

    ============================================= 原文链接: 为什么覆写equals必须要覆写hashCode? 转载请注明出处! ============= ...

  6. Atitit.如何文章写好 论文 文章 如何写好论文 技术博客 v4

    Atitit.如何文章写好 论文  文章  如何写好论文 技术博客 1. 原则 2 1.1. 有深度, 有广度 2 1.2. 业务通用性有通用性 尽可能向上抽象一俩层..业务通用性与语言通用性. 2 ...

  7. Atitit.如何文章写好 论文 文章 如何写好论文 技术博客

    Atitit.如何文章写好 论文  文章  如何写好论文 技术博客 1. 原则 1 1.1. 有深度, 有广度 1 1.2. 业务通用性有通用性 尽可能向上抽象一俩层..业务通用性与语言通用性. 2 ...

  8. python手写神经网络实现识别手写数字

    写在开头:这个实验和matlab手写神经网络实现识别手写数字一样. 实验说明 一直想自己写一个神经网络来实现手写数字的识别,而不是套用别人的框架.恰巧前几天,有幸从同学那拿到5000张已经贴好标签的手 ...

  9. 用JAVA写一个多线程程序,写四个线程,其中二个对一个变量加1,另外二个对一个变量减1

    package com.ljn.base; /** * @author lijinnan * @date:2013-9-12 上午9:55:32 */ public class IncDecThrea ...

随机推荐

  1. Java 集合系列04之 fail-fast总结(通过ArrayList来说明fail-fast的原理、解决办法)

    概要 前面,我们已经学习了ArrayList.接下来,我们以ArrayList为例,对Iterator的fail-fast机制进行了解.内容包括::1 fail-fast简介2 fail-fast示例 ...

  2. Object-C 1.0 第二章

    1.  输出obc #import <Foundation/Foundation.h> int main(int argc,const char *argv[]) { NSLog(@&qu ...

  3. 什么是BFC?(转载)

    在解释 BFC 是什么之前,需要先介绍 Box.Formatting Context的概念. Box: CSS布局的基本单位 Box 是 CSS 布局的对象和基本单位, 直观点来说,就是一个页面是由很 ...

  4. WebCrawler

    WebCrawler WebCrawler is a metasearch engine that blends the top search results from Google Search a ...

  5. 【MongoDB:第二天】基本操作

    接上一篇博客: http://www.cnblogs.com/xiaoit/p/3867573.html 1:插入新的数据 db.person.insert({"uid" : 12 ...

  6. RAC初体验

    什么是RAC? 几乎每一篇介绍RAC的文章开头都是这么一个问题.我这篇文章是写给新手(包括我自己)看的,所以这个问题更是无法忽视. 简单的说,RAC就是一个第三方库,他可以大大简化你的代码过程. 官方 ...

  7. PHP性能监测的工具介绍 - XHProf

    http://segmentfault.com/a/1190000003509917 http://www.cnblogs.com/casatwy/archive/2013/01/17/2865241 ...

  8. 如何使用QQ号进行快捷登录

    注意:第三步回调地址域名必须严格按照规范填写否则无法使用.QQ回调地址域名为您的网站地址无论您是否为DISCUZ论坛,请全部使用网站申请.discuz论坛申请的KEY无效注册QQ登录应用将会获得该应用 ...

  9. IntelliJ IDEA使用小技巧

    1:设置类,接口,枚举注解模板 #if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")package ${PACKAGE_NA ...

  10. 简单快速部署samba服务器

    samba是一种在linux环境运行的免费软件,可以为局域网内的不同计算机系统之间提供文件以及打印机等资源的共享服务. samba服务安装和配置: 1.安装gcc编译器以及samba服务和samba依 ...