小心翼翼地重载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重载应该不改变原有签名,而是直接无缝替换系统原有版本,如下所示:

 
 
  1. void * operator new(size_t size)  
  2. {  
  3.      if(size == 0)  
  4.       size = 1;  
  5.      void *res;  
  6.      for(;;)  
  7.      {  
  8.        //allocate memory block  
  9.        res = heap_alloc(size);  
  10.        //if successful allocation, return pointer to memory  
  11.        if(res)  
  12.            break;  
  13.       //call installed new handler  
  14.     if (!CallNewHandler(size))  
  15.         break;  
  16.     //new handler was successful -- try to allocate again  
  17.  }  
  18.  return res;  

如果是用这种方式进行的重载,再使用时就不需要包含new头文件了。“性能优化”时通常采用这种方式。

如果重载了一个operator new,记得一定要在相同的范围内重载operator delete。因为你分配出来的内存只有你自己才知道应该如何释放。如果你偷懒或者是忘记了,编译器就会求助于默认的operator delete,用默认方式释放内存。虽然程序编译可以通过,但是这将导致惨重的代价。所以,你必须时刻记得在写下operator new的同时写下operator delete。相对于operator new,重载operator delete要简单许多,如下所示:

 
 
  1. void operator delete(void* p)  
  2. {  
  3.       if(p==NULL)  
  4.          return;  
  5.       free(p);  

唯一要注意的一点就是,须遵循C++标准中要求删除一个NULL指针是安全的这一规定。在全局空间中重载void * operator new(size_t size)函数将会改变所有默认的operator new的行为方式,所以一定要小心使用。

如果使用不同的参数类型重载operator new/delete,则请采用如下函数声明形式:

 
 
  1. //返回的指针必须能被普通的 ::operator delete(void*) 释放  
  2. void* operator new(size_t size, const char* file, int line);  
  3. //析构函数抛异常时被调用  
  4. void operator delete(void* p, const char* file, int line); 

调用时采用以下方式:

 
 
  1. string* pStr = new (__FILE, __LINE__) string; 

这样就能跟踪内存分配的具体位置,定位这个动作发生在哪个文件的哪一行代码中了。在“检测内存错误”和“统计内存使用数据”时通常会用这种方式重载。

此外,我们还可以为operator new的重载使用参数默认值,甚至是不定参数。其原则和普通函数重载一样。

但是在使用全局重载时应该慎之又慎,因为这样做非常具有侵略性:这会让使用你编写的库的人没有选择的余地;同时,如果两个lib中都对operator new进行了重载,在使用时会出现这样的错误:duplicated symbol link error。这是多么令人恼火的一件事啊。

与全局 ::operator new() 不同,具体类的operator new与 delete的影响面要小得多,它只影响本class及其派生类。为某个class重载operator new时必须将其定义为类的静态函数。因为operator new是在类的具体对象被构建出来之前调用的,在调用operator new的时候this指针尚未诞生,因此重载的 operator new必须是static的:

 
 
  1. class B  
  2. {  
  3. public:  
  4.      static void * operator new(size_t size);  
  5.      static void operator delete(void *p);  
  6.      // other members  
  7. };  
  8. void *B::operator new(size_t size)  
  9. {  
  10.     ...  
  11. }  
  12. void B::operator delete(void *p)  
  13. {  
  14.     ...  

当然,同全局operator new重载一样,在类中重载成员operator new也可以添加额外的参数,并且可以使用默认值。另外,成员operator new也是可以继承的。但类中的operator delete也必须声明为静态函数。因为调用operator delete时,对象已经被析构,this指针业已灰飞烟灭。

虽然为单独的class重载成员operator new/ delete是可行的,但不推荐使用。因为既然对它们进行了重载,说明它的内存分配策略已被进行了精心的特殊定制,从类似ClassName * p =new ClassName形式的代码中我们根本不能获得此信息。而且,我们有更加简单明了的Factory方案可以使用:

 
 
  1. static ClassName* ClassName::CreateObject(); 

清晰明确优于模糊不清(Explicit is better than implicit),对此我深信不疑。

关于operator new/operator delete的重载,还有一个必须小心的问题,那就是在内存分配机制中必须要考虑对象数组内存分配这一点。C++将对象数组的内存分配看作是一个不同于单个对象内存分配的单独操作。对于多数的C++实现,因为需要额外存储对象数量,new[]操作符中的个数参数会是数组的大小加上存储对象数目的一些字节。所以,如果希望改变对象数组的分配方式,同样需要重载new[]和 delete[]操作符。

请记住:

通过重载operator new 和operator delete的方法,可以自由地采用不同的分配策略,从不同的内存池中分配不同的类对象。但是是否选择重载operator new/delete一定要深思熟虑。

 

原文:http://book.51cto.com/art/201202/317799.htm

在C++中,可以重载newdelete运算符以定制动态内存管理的行为。重载new运算符可以用于自定义内存分配的方式,而重载delete运算符可以用于自定义内存释放的方式。 重载new运算符的一种常见方式是定义一个全局的new运算符函数,并使用该函数来执行内存分配。例如: ```cpp void* operator new(size_t size) { // 自定义内存分配逻辑 void* ptr = malloc(size); // 检查分配是否成功 if (ptr == nullptr) { throw std::bad_alloc(); } return ptr; } ``` 重载delete运算符的一种常见方式是定义一个全局的delete运算符函数,并使用该函数来执行内存释放。例如: ```cpp void operator delete(void* ptr) noexcept { // 自定义内存释放逻辑 free(ptr); } ``` 需要注意的是,如果重载new运算符,通常也需要相应地重载delete运算符,以确保内存的正确释放。 可以根据需要重载其他版本的newdelete运算符,例如带有额外参数的newdelete运算符,数组形式的newdelete运算符等。重载这些运算符时需要遵循一定的规则和约定,确保正确性和可靠性。 值得注意的是,C++11引入了更加灵活和安全的内存管理方式,例如智能指针(如std::shared_ptr和std::unique_ptr)和RAII(资源获取即初始化)等,这些方式可以减少手动管理内存的复杂性和错误。因此,在使用newdelete运算符进行内存管理之前,建议先考虑这些更高级的内存管理工具。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值