设为首页 加入收藏

TOP

Effective C++ 条款 52:写了placement new也要写placement delete(一)
2015-07-20 17:35:26 来源: 作者: 【 】 浏览:2
Tags:Effective 条款 写了 placement new delete

(一)

当我们写下了下面这个语句:

Widget* pw = new Widget;

这个时候,共有两个函数被调用:一个分配内存的operator new,另外一个是Widget的default构造函数。

假设第一个调用成功,第二个却抛出异常。步骤一所分配内存必须取消并恢复旧观,否则会造成内存泄漏。这时,客户没能力归还内存,因为Widget构造函数抛出异常,pw尚未被赋值,客户手上也就没有指针指向该被归还的内存。

这个时候,取消步骤一,并恢复旧观的责任就落到C++运行系统身上。

运行期系统会高兴的调用步骤一所调用operator new的相应的operator delete版本,前提是他必须知道哪一个operator delete被调用(可能有许多个)。

如果目前面对的是拥有正常签名式的new和delete,并不是问题,

正常的new和对应正常的delete:

void* operator new(std::size_t) throw(std::bad_alloc);
void operator delete(void* rawMemory) throw();//global 作用域中的正常签名式
void operator delete(void* rawMemory, std::size_t size) throw();//class作用域中的典型签名式。


(二)

但是当你开始声明非正常形式的operator new,也就是带附加参数的operator new。

这个时候,“究竟哪一个delete伴随这个new”的问题就浮现了:

比如说:

class专属的operator new要求接受一个ostream,志记相关分配信息,同时又写了个正常形式的class专属operator 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, std::size_t size) throw(); 
};

如果operator new接受的参数除了一定会有的那个size_t之外还有其他,这个便是所谓的placement new。

众多placement new中特别有一个是“接受一个指针指向对象该被构造之处”,那样的operator new长相如下:

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

Widget* pw = new(std::cerr) Widget;

这个如果Widget构造函数抛出异常,运行期系统无法知道真正被调用的那个new如何运作,因此无法取消分配并恢复旧观。运行期系统寻找“参数个数和类型都与operator new相同的”某个operator delete。应该是:

void operator delete(void*, std::ostream&) throw();

被称为placement deletes。

现在,既然Widget没有声明placement版本的operator delete,所以运行期系统什么也不做。如果Widget构造函数抛出异常,不会有任何operator delete被调用。

所以会造成内存泄漏。

(三)

规则很简单:如果一个带额外参数的operator new没有“带相同额外参数”的对应版operator delete,那么当new的内存分配动作需要取消并恢复时就没有任何operator delete会被调用。所以有必要声明一个placement delete,对应于那个有志记功能的placement new:

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

这样改变后,如果Widget构造函数抛出异常:

Widget* pw = new (std::cerr) Widget;

对应的placement delete会被自动调用,让Widget有机会确保不泄露内存。

这个语句:

delete pw;//调用正常的operator delete

placement delete只有在“伴随placement new调用而触发的构造函数”出现异常时才会被调用。对一个指针施行delete绝不会导致调用placement delete。

这意味对所有placement new我们必须同时提供一个正常的delete和一个placement版本。

(四)

由于成员函数的名称会掩盖其外围作用域中的相同名称,你必须小心让class专属的news掩盖客户期望的其他news(包括正常版本)。

例如你有一个base class,其中声明唯一一个placement operator new,客户会发现他们无法使用正常形式的new:

class Base {
public:
	...
	static void* operator new(size_t size, ostream& logStream) throw(bad_alloc);  //这个new会遮掩正常的global形式
};

Base* pb = new Base;               //错误!因为正常形式的operator new被遮掩
Base* pb = new (std::cerr) Base;   //正确,调用Base的placement new.

同样的道理,derived classes中的operator news会遮掩global版本和继承而得的operator new版本:

class Derived : public Base {     //继承先前的Base
public:
	...
	static void* operator new(std::size_t size) throw(std::bad_alloc);   重新声明正常形式的new
	...
};

Derived* pd = new(std::clog) Derived;   //错误,因为Base的placement被遮掩了。
Derived* pd = new Derived;    //没问题,调用Derived的operator new.


(五)

缺省情况下c++在global作用域内提供以下形式的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, const std::nothrow_t&) throw();// nothrow new

如果你在c

首页 上一页 1 2 下一页 尾页 1/2/2
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
分享到: 
上一篇POJ2386 Lake Counting(DFS) 下一篇冒泡排序-插入排序-快速排序-选择..

评论

帐  号: 密码: (新用户注册)
验 证 码:
表  情:
内  容:

·Java 并发工具类:提 (2025-12-25 20:25:44)
·Java面试技巧:如何 (2025-12-25 20:25:41)
·Java并发编程中的线 (2025-12-25 20:25:38)
·C 语言 - cppreferen (2025-12-25 19:50:27)
·《C 语言入门教程》 (2025-12-25 19:50:23)