• 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.
    ";
        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

  • 相关阅读:
    深入浅出理解依赖注入这种由外部负责其依赖需求的行为,我们可以称其为 “控制反转(IoC)”
    php 远程下载图片到本地
    深入理解 RESTful Api 架构
    uva 10369 Arctic Network (最小生成树加丁点变形)
    UVALive
    UVA
    UVA
    POJ 1182 食物链(经典带权并查集 向量思维模式 很重要)
    HDU 1829 A Bug's Life (种类并查集)
    UVA
  • 原文地址:https://www.cnblogs.com/shijingjing07/p/5519153.html
Copyright © 2020-2023  润新知