建议33:小心翼翼地重载operator new/ operator delete
虽然C++标准库已经为我们提供了new与delete操作符的标准实现,但是由于缺乏对具体对象的具体分析,系统默认提供的分配器在时间和空间两方面都存在着一些问题:分配器速度较慢,而且在分配小型对象时空间浪费比较严重,特别是在一些对效率或内存有较大限制的特殊应用中。比如说在嵌入式的系统中,由于内存限制,频繁地进行不定大小的内存动态分配很可能会引起严重问题,甚至出现堆破碎的风险;再比如在游戏设计中,效率绝对是一个必须要考虑的问题,而标准new与delete操作符的实现却存在着天生的效率缺陷。此时,我们可以求助于new与delete操作符的重载,它们给程序带来更灵活的内存分配控制。除了改善效率,重载new与delete还可能存在以下两点原因:
检测代码中的内存错误。
获得内存使用的统计数据。
相对于其他的操作符,operator new具有一定的特殊性,在多个方面上与它们大不相同。首先,对于用户自定义类型,如果不重载,其他操作符是无法使用的,而operator new则不然,即使不重载,亦可用于用户自定义类型。其次,在参数方面,重载其他操作符时参数的个数必须是固定的,而operator new的参数个数却可以是任意的,只需要保证第一个参数为size_t类型,返回类型为void *类型即可。所以operator new的重载会给我们一种错觉:它更像是一个函数重载,而不是一个操作符重载。
关于operator new重载函数的形式,在C++标准中有如下规定:
分配函数应当是一个类的成员函数或者是全局函数;如果一个分配函数被放于非全局名空间中,或者是在全局名空间被声明为静态,那这个程序就是格式错误的。
也就是说,重载的operator new必须是类成员函数或全局函数,而不可以是某一名空间之内的函数或是全局静态函数。此外,还要多加注意的是,重载operator new时需要兼容默认的 operator new的错误处理方式,并且要满足C++的标准规定:当要求的内存大小为0 byte时也应该返回有效的内存地址。
所以,全局的operator new重载应该不改变原有签名,而是直接无缝替换系统原有版本,如下所示:
|
如果是用这种方式进行的重载,再使用时就不需要包含new头文件了。“性能优化”时通常采用这种方式。
如果重载了一个operator new,记得一定要在相同的范围内重载operator delete。因为你分配出来的内存只有你自己才知道应该如何释放。如果你偷懒或者是忘记了,编译器就会求助于默认的operator delete,用默认方式释放内存。虽然程序编译可以通过,但是这将导致惨重的代价。所以,你必须时刻记得在写下operator new的同时写下operator delete。相对于operator new,重载operator delete要简单许多,如下所示:
|
唯一要注意的一点就是,须遵循C++标准中要求删除一个NULL指针是安全的这一规定。在全局空间中重载void * operator new(size_t size)函数将会改变所有默认的operator new的行为方式,所以一定要小心使用。
如果使用不同的参数类型重载operator new/delete,则请采用如下函数声明形式:
|