八、定制new和delete
Item49. 了解new-handler的行为
当然operator new抛出异常以反映内存需求出错之前,会先调用一个客户指定的错误处理函数(new-handler)。
namespace std {
typedef void (*new_handler)();
new_handler set_new_handler(new_handler p) throw();
}
以下是使用示例:
当operator new无法满足内存申请是,它会不断调用new-handler函数,直到找到足够内存。一个设计良好的new-handler函数必须做以下事情:
1. 让更多内存可被使用。
2. 安装另一个new-handler。获悉另一个有能力得到更多内存的new-handler。
3. 卸除new-handler。即设为null指针,这时operator new会在内存分配不成功时抛出异常。
4. 抛出bad_alloc(或其子类)的异常。这样的异常不会被operator new捕捉,因此会被传播到内存索求处。
5. 不返回。通常调用abort或exit。
如果希望new-handler属于一个class,可以如下:
有了以上的class template,为Widget添加set_new_handler支持能力就轻而易举了:
class Widget: public NewHandlerSupport<Widget> {
... // as before, but without declarations for
}; // set_new_handler or operator new
几点说明:
1. 类型参数T从未被使用。它只是用来区分不同的derived class。
2. Widget继承自一个templatized base class,而后者又以Widget作为类型参数,这种技术叫curiously
recurring template pattern (CRTP)。(Meyers建议用Do It For Me)
Item50. 了解new和delete的合理替换时机
三个常见的理由:
1. 用来检测运用上的错误。可以自定一个operator new, 可以超额分配内存,以额外空间放置特定的byte
patterns(signature)。operator delete得以检查上述签名是否原封不动。
2. 为了强化效能。默认的只能对大部分程序合适。Boost的Pool分配器就对于最常见的“分配大量小型对象”很有帮助。
3. 为了收集使用上的统计数据。
以下是一个global ooperator new,促进并协助检测“overruns”和“underruns”,
这段代码没有考虑齐位,C++要求所有operator new返回的指针都有适当的对齐。TR1支持各类型特定的对齐条件。
小结:何时可以在“全局性的”或“class专属的”基础上合理替换缺省的new和delete——
1. 为了检测运用错误
2. 为了收集动态分配内存之使用统计信息
3. 为了增加分配和归还的速度
4. 为了降低缺省内存管理器带来的空间额外开销
5. 为了弥补缺省分配器中的非最佳齐位
6. 为了将相关对象成簇集中(placement版本)
7. 为了获得非传统的行为
(thy:记住80-20原则)
Item52. 编写new和delete时需固守常规
1. operator new——
必须返回正确的值。有能力就返回指针,没能力就抛出bad_alloc(实际上operator new在每次失败后调用
new-handling函数,只有当new-handling函数的指针为null时,operator new才抛出异常)。C++标准规定即使客户要求0 bytes,也得返回一个合法指针。
但是,针对class X设计的operator new,其行为很典型地只为大小刚好为sizeof(X)的对象而设计。然而一旦被继承下去:
所以为了考虑这种情况:
注意C++规定所有非附属(独立式)(freestading)对象必须有非零大小,所以sizeof(Base)不可能为0。
operator new[]要复杂得多,因为如果你申请一块内存,你不能保证它能为size_t个对象所用,因为可能被继承。
2. operator delete——
要简单得多,全局版本:
member版本:
记住:如果base class没有virtual析构函数,则继承后C++传给operator delete的size_t数值可能不正确(Item7)。
Item52. 写了placement new也要写placement delete
如果你有一个正常的void* operator new(std::size_t) throw(std::bad_alloc);
系统会找到相应的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
但如果你有了非正常的new:
对应的delete应该是:
void operator delete(void *, std::ostream&) throw();
上面的Widget中没有,所以如果Widget构造函数抛出异常,则不会有任何operator delete被调用。
正确设计如下:
不过如果客户调用delete pw; // invokes the normal operator delete
则只有正常形式的operator delete调用,因为placement delete只有在“伴随placement new调用而触发的构造函数”出现异常时才会被调用。
这意味着我们要同时提供正常的版本和placement版本。后者的额外参数必须和operator new一样。
最终,为了避免掩盖base class和global中的new和delete,我们建立一个base class:
在使用时: