C++ new操作符详解

一.new操作符的概念

我们通常讲的new是指的是new operator,其实还有另外两个概念,operator new 和 placement new。

1、new operator        

我们在使用new operator的时候,实际上是执行了三个步骤:

1)调用operator new分配内存 ;2)调用构造函数生成类对象;3)返回相应指针。

2、operator new

所以说operator new做的事情是new operator的一部分。

operator new的原型是

Void* operator new(size_t size);

参数size指定待分配的内存大小,函数内部调用malloc初始化内存,返回指向这个内存的指针。

你可以重载这个函数(注意是重载operator new,而不能重载new operator)。operator new默认情况下首先调用分配内存的代码,尝试得到一段堆上的空间,如果成功就返回,如果失败,则转而去调用一个new_hander(异常处理函数),若没有定义new_hander,则抛出异常,否则执行new_hander,然后继续重复前面过程。你可以在重载的时候加上额外的参数,但是第一个参数类型必须是size_t.例如:

class A
{
public:
   void* operator new(size_t size)
   {
       printf("operator new calledn");
       return ::operator new(size);
   }
};
A* a = new A();

这里通过::operator new调用了原有的全局的new,实现了在分配内存之前输出一句话。全局的operator new也是可以重载的,但这样一来就不能再递归的使用new来分配内存,而只能使用malloc了:

void* operator new(size_t size)
{
   printf("global newn");
   return malloc(size);
}

相应的,delete也有delete operator和operator delete之分,后者也是可以重载的。并且,如果重载了operator new,就应该也相应的重载operator delete,这是良好的编程习惯。

 

3、placement new

placement new是用来实现定位构造的,因此可以实现new operator三步操作中的第二步。

其实它也只是operator new的一个重载的版本,只是我们很少用到它。如果你想在已经分配的内存中创建一个对象,使用new时行不通的。也就是说placement new允许你在一个已经分配好的内存中(栈或者堆中)构造一个新的对象。原型中void*p实际上就是指向一个已经分配好的内存缓冲区的的首地址。

 

我 们知道使用new操作符分配内存需要在堆中查找足够大的剩余空间,这个操作速度是很慢的,而且有可能出现无法分配内存的异常(空间不够)。 placement new就可以解决这个问题。我们构造对象都是在一个预先准备好了的内存缓冲区中进行,不需要查找内存,内存分配的时间是常数;而且不会出现在程序运行中途 出现内存不足的异常。所以,placement new非常适合那些对时间要求比较高,长时间运行不希望被打断的应用程序。

使用方法如下:
1. 缓冲区提前分配
可以使用堆的空间,也可以使用栈的空间,所以分配方式有如下两种:

class MyClass {…};
char *buf=new char[N*sizeof(MyClass)+sizeof(int)];或者char buf[N*sizeof(MyClass)+sizeof(int)];

2. 对象的构造

MyClass * pClass=new(buf) MyClass;

3. 对象的销毁
一旦这个对象使用完毕,你必须显式的调用类的析构函数进行销毁对象。但此时内存空间不会被释放,以便其他的对象的构造。

pClass->~MyClass();

4. 内存的释放
如果缓冲区在堆中,那么调用delete[] buf;进行内存的释放;如果在栈中,那么在其作用域内有效,跳出作用域,内存自动释放。

注意:

    • 在C++标准中,对于placement operator new []有如下的说明: placement operator new[] needs implementation-defined amount of additional storage to save a size of array. 所以我们必须申请比原始对象大小多出sizeof(int)个字节来存放对象的个数,或者说数组的大小。
    • 使用方法第二步中的new才是placement new,其实是没有申请内存的,只是调用了构造函数,返回一个指向已经分配好的内存的一个指针,所以对象销毁的时候不需要调用delete释放空间,但必须调用析构函数销毁对象

placement new 是重载operator new 的一个标准、全局的版本,它不能够被自定义的版本代替(不像普通版本的operator new 和 operator delete能够被替换)。
void *operator new( size_t, void *p ) throw()     { return p; }
placement new的执行忽略了size_t参数,只使用第二个参数。其结果是允许用户把一个对象放到一个特定的地方,达到调用构造函数的效果。
和其他普通的new不同的是,它在括号里多了另外一个参数。比如:
Widget * p = new Widget; - - - - - - - - - //ordinary new 
pi = new (ptr) int; pi = new (ptr) int;     //placement new

括号里的参数ptr是一个指针,它指向一个内存缓冲器,placement new将在这个缓冲器上分配一个对象。Placement new的返回值是这个被构造对象的地址(比如括号中的传递参数)。placement new主要适用于:在对时间要求非常高的应用程序中,因为这些程序分配的时间是确定的;长时间运行而不被打断的程序;

 

三、处理内存分配异常

正如前面所说,operator new的默认行为是请求分配内存,如果成功就返回,如果失败,则转而去调用一个new_hander(异常处理函数),若没有定义new_hander,则抛出异常,否则执行new_hander,然后继续重复前面过程。于是,想要从operator new的执行过程中返回,则必然需要满足下列条件之一:

1)分配内存成功

2)new_handler中抛出bad_alloc异常

3)new_handler中调用exit()或类似的函数,使程序结束

于是,我们可以假设默认情况下operator new的行为是这样的:

    void* operator new(size_t size)  
    {  
       void* p = null  
       while(!(p = malloc(size)))  
       {  
           if(null == new_handler)  
              throw bad_alloc();  
           try  
           {  
              new_handler();  
           }  
           catch(bad_alloc e)  
           {  
              throw e;  
           }  
           catch(…)  
           {}  
       }  
       return p;  
    }  

当operator new不能满足一个内存分配请求的时候,默认会抛出一个异常,我们可以通过设置new_handler定义处置策略。

new_handler的模型为:void (*new_handler)()

可以通过“void set_new_handler( void(*new_handler)()) throw();”设置这个处理函数(new_handler),它定义在<new>标准函数库中:

namespace std
{
    void (*new_handler)();
    void set_new_handler( new_handler )throw();
}
//error-handling function
void MemErrorHandling()
{
    std::cerr << "Failed to allocate memory.\n";
    std::abort();
}
... ...
std::set_new_handler(MemErrorHandling);

现在我们知道了new操作失败后,系统地大概处理流程,以及怎么设置用户自定义处理函数,但是我们究竟可以在new_handler中做些什么处理呢?

1、删除其它无用的内存,使系统具有可以更多的内存可以使用,为下一步的内存申请作准备。
2、设置另外一个new_handler。如果当前的new_handler不能够做到更多的内存申请操作,或者它知道另外一个new_handler可 以做到,则可以调用set_new_handler函数设置另外一个new_handler,这样在operator new下一次调用的时候,可以使用这个新的new_handler。
3、卸载new_handler(通过set_new_handler(0)),使operator new在下一次调用的时候,因为new_handler为空抛出内存申请异常。
4、抛出自定义异常。
5、不再返回,调用abort或者exit退出程序。

 

参考:
1、http://www.bc-cn.net/Article/kfyy/cjj/jszl/200604/4002.html

2、http://blog.csdn.net/youdianmengxiangba/article/details/8233651

 

posted on 2016-05-23 10:58  迪米特  阅读(6130)  评论(1编辑  收藏  举报

导航