Customizing new and delete-49,51,52

当计算环境(Java和.NET)夸耀自己内置“垃圾回收能力”的当今,C++对内存管理的纯手工法也许看起来有点老气。但是许多苛刻的系统程序开发人员之所以选择C++,就是因为它允许他们手工管理内存。

这样做的前提是,了解C++内存管理例程的行为。这部分内容涉及的两个主角是分配例程和归还例程(allocation and deallocation routines, 也就是operator new和operator delete),配角是new-handler,这是当operator new无法满足客户的内存需求时所调用的函数。

另外,operator new和operator delete只适合用来分配单一对象。Arrays所用的内存由operator new[]分配,并由operator delete[]归还。除非特别提醒,否则后面关于operator new和operator delete的情况都适用于operator new[]和operator delete[]。

最后,STL容器所使用的heap内存是由容器所拥有的分配器对象(allocator objects)管理,不是被new和delete直接管理。

49.Understand the behavior of the new-handler.(了解new-handler的行为)

(1)当operator new无法满足某一内存分配需求时,它会抛出异常。当opeartor new抛出异常以反映一个未获满足的内存需求之前,它会先调用一个客户指定的错误处理函数,一个所谓的new-handler。为了指定这个“用以处理内存不足”的函数,客户必须调用set_new_handler,那是声明于<new>的一个标准程序库函数:

1)其中,new_handler是个typedef,定义出一个指针指向函数,该函数没有参数也不返回任何东西;
2)set_new_handler则是“获得一个new_handler并返回一个new_handler”的函数;
3)throw()是一份异常明细,表示该函数不抛出任何异常。
4)set_new_handler的参数是个指针,指向operator new无法分配足够内存时该被调用的函数。其返回值也是个指针,指向set_new_handler被调用前正在执行(但马上要被替换)的那个new_handler函数。

(2)我们可以这样使用set_new_handler:

如果operator new无法为100000000个整数分配足够空间,outOfMem会被调用。

(3)当operator new无法满足内存申请时,它会不断调用new_handler函数,直到找到足够内存。设计一个良好的new_handler函数必须做以下事情:

1)让更多内存可被使用。这便造成operator new内的下一次内存分配动作可能成功。实现此策略的一个做法是,程序一开始执行就分配一大块内存,而后但new_handler第一次被调用,将它们释还给程序使用。

2)安装另一个new_handler。如果目前的new_handler无法取得更多可用内存,或许他知道另外哪个new_handler有此能力。

3)卸除new_handler。也就是将null指针传给set_new_handler。一旦没有安装任何new_handler,operator new会在内存分配不成功时抛出异常。

4)抛出bad_alloc(或派生自bad_alloc)的异常。这样的异常不会被operator new捕捉,因此会被传播到内存索求处。

5)不返回,通常调用abort或exit。

请记住:
set_new_handler允许客户指定一个函数,在内存分配无法获得满足时被调用。

51.Adhere to convention when writing new and delete.(编写new和delete时需固守常规)

请记住:
(1)operator new应该内含一个无穷循环,并在其中尝试分配内存,如果它无法满足内存需求就该调用new_handler。它也应该有能力处理0 bytes申请。Class专属版本则还应该处理“比正确大小更大的(错误)申请”。
(2)operator delete应该在收到null指针时不做任何事。Class专属版本则还应该处理“比正确大小更大的(错误)申请”。

52.Write placement delete if you write placement new.(写了placement new 也要写 placement delete)

(1)我们知道,当我们写了一个new表达式:
Widget *pw = new Widget;
共有两个函数被调用:一个是用以分配内存的operator new,一个是Widget的default构造函数。

1)假设其中第一个函数调用成功,第二个函数却抛出异常。既然那样,步骤一的内存分配所得必须取消并恢复旧观,否则会造成内存泄露memory leak。

2)但此时,客户没有能力归还内存,因为如果Widget构造函数抛出异常,pw尚未被赋值,客户手上也就没有指针指向该被归还的内存。因此,取消步骤一并恢复旧观的责任落在了C++运行期系统身上。

3)运行期系统会调用步骤一所调用的operator new的相应的operator delete版本。

因此,当你使用正常形式的new和delete,运行期系统毫无疑问可以找出那个“知道如何取消new所作所为并恢复旧观”的delete。

然后,当我们申明非正常形式的operator new,“究竟哪一个delete伴随这个new”的问题便出现了。

(2)如果operator new接受的参数除了一定会有的那个size_t之外还有其他,这便是所为的placement new。众多placement new版本中特别有用的一个是“接受一个指针指向对象该被构造之处”,如下所示:

这个版本已被纳入C++标准程序库,只要#include <new>就可以使用它。这个new的用途之一是负责在vector的未使用空间上创建对象。它同时也是最早的placement new版本。

(3)类似于new的placement版本,operator delete如果接受额外参数,便称为placement delete。

(4)综上,有个规则:如果一个带额外参数的operator new没有“带相同额外参数”的对应版operator delete, 那么当new的内存分配动作需要取消并恢复旧观时就没有任何operator delete会被调用。因此,针对这种情况,有必要声明一个placement delete。

请记住:
(1)当你写一个placement operator new, 请确定也写出了对应的placement operator delete。如果没有这样做,你的程序可能会发生内存泄露。
(2)当你声明placement new和placement delete,请确定不要无意识地遮掩它们的正常版本。

发表评论

电子邮件地址不会被公开。 必填项已用*标注