C/C++编程:写了placement new也要写placement delete

1059 篇文章 285 订阅

对于:

Widget *pw = new Widget ;

共有两个函数被调用:一个是用以分配的operator new,一个是Widget的默认构造函数

假设第一个函数调用成功,第二个函数抛出异常-----第一步的内存分配所得的内存必须归还,否则内存泄漏。此时,客户没有能力归还内存,因为如果Widget构造函数抛出异常,pw尚未被赋值,客户手上也没有指针指向应该被归还的内存。取消第一步并恢复到原来的责任因此落在C++运行期系统上。

运行期系统会调用步骤一所调用的operator new的响应的operator delete版本,如果目前面对的是拥有正常签名式(signature)的new和delete,这并不是问题,因为正常的operator new:

void * operator new(std::size_t) throw(std::bad_alloc);

对应正常的operator delete:

void operator delete(void * ramMemory) throw(); //global作用域中的正常签名式
void operator delete(void * ramMemory, std::size_t size) throw(); //class作用域中的正常签名式

然而,当你声明非正常形式的operator new,也就是带有附加参数的operator new,那么找到对应的delete可能有点困难。

举个例子,假设你写了一个class专属的operator new,要求接受一个ostream,用来log相关分配信息,同时右写了一个正常形式的class专属operator delete:

// 这个设计有问题
class Widget{
public:
	static void * operator new(std::size_t size, std::ostream& logStream) throw(std::bad_alloc);  // 非正常适应的delete
	static void  operator delete(void *pMemory, std::size_t) throw(); //正常的class专属delete
};

如果operator new接受的参数除了一定会有的那个size_t之外还有其他,这就是所谓的placement new。因此,上面的operator new是个placement版本。众多placement new版本中特别有用的一个是“接受一个指针指向对象该被构造之处”,长相如下:

void * operator new(std::size_t, void *pMemory) throw();

这个版本的new已经被纳入C++标准库,你只要#include<new>即可。这个new的用途之一是负责在vector的未使用空间上创建对象。它同时也是最早版本的placement new。实际上它是这个函数的命名由来:一个特定位置的new。当人们谈到placement new,就是谈这个版本,也就是“唯一额外参数是个void*”,少数情况下才是接受任意实参的operator new。

现在我们回到原先那个设计有问题的Widget class。这里的问题是:这个class将会引起微妙的内存泄漏。考虑如下代码,它在动态创建一个widget时将相关的log记在cerr:

Widget *pw = new(std::cerr) Widget; //调用operator new并传递cerr为ostream实参:这个动作会在widget构造函数抛出异常时泄漏内存。

但是,如果内存分配成功,widget构造函数抛出异常,运行期系统将寻找“参数类型与个数都与operator new相同的”operator delete。也就是说:

static void * operator delete(std::size_t size, std::ostream& logStream) throw()

但是找不到,所以它什么也不做,从而造成内存泄漏

如果内存分配成功,widget构造函数没有抛出异常,当客户调用:

delete pw;  

调用的是正常使用的operator delete,而不是placement delete。placement delete只在“伴随placement new调用而触发的构造函数”出现异常时才会被调用。

也就是说,要解决placement new的内存泄漏,必须同时提供一个正常的operator delete(用于构造期间无任何异常时抛出)和一个placement delete(用于构造期间有异常抛出)。

另外,由于成员函数的名称会掩盖作用域中的相同名称,如果你在基类中声明唯一一个placement operator new,客户讲无法使用正常形式的new:

class Base{
public:
	static void * operator new(std::size_t size, std::ostream& logStream) throw(std::bad_alloc);
	};

Base *pb = new Base; // errror,因为正常形式的operator new被掩盖
Base *pb = new(std::cerr)Base; //ok,调用Base的placement 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”,虽然它们的函数签名不同。

最佳实践

缺省情况下C++在全局作用域

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

如果你在类内声明了任何operator new,它会遮掩上面这些标准形式。除非你的意思就是要阻止class的客户使用这些形式,否则请确保它们在你所生成的任何定制new之外还可用,对于每一个operator new也请确定提供对应的operator 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() { 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); }
};

凡是想以自定形式扩充标准的客户,可以利用继承机制以及using声明式取得标准形式:

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
};
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值