zoukankan      html  css  js  c++  java
  • [C++空间分配]new运算符、operator new、placement new的区别于联系

    先科普一下:

    1. new的执行过程:

    (1)通过operator new申请内存

    (2)使用placement new调用构造函数(内置类型忽略此步)

    (3)返回内存指针

     

    2. new和malloc的比较:

    (1)new失败时会调用new_handler处理函数,malloc不会,失败时返回NULL

    (2)new能通过placement new自动调用对象的构造函数,malloc不会

    (3)new出来的东西是带类型的,malloc是void*,需要强制转换

    (4)new是C++运算符,malloc是C标准库函数

     

    3. delete的执行过程:

    (1)调用析构函数(内置类型忽略此步)

    (2)释放内存

     

    4.delete和free的比较

    (1)delete能自动调用对象的析构函数,malloc不会

    (2)delete是C++运算符,free是C标准库函数


    new operator/delete operator就是new和delete操作符,而operator new/operator delete是函数。

    new 运算符
    (1)调用operator new分配足够的空间,并调用相关对象的构造函数
    (2)不可以被重载

    operator new
    (1)只分配所要求的空间,不调用相关对象的构造函数。当无法满足所要求分配的空间时,则
            ->如果有new_handler,则调用new_handler,否则
            ->如果没要求不抛出异常(以nothrow参数表达),则执行bad_alloc异常,否则
            ->返回0
    (2)可以被重载
    (3)重载时,返回类型必须声明为void*
    (4)重载时,第一个参数类型必须为表达要求分配空间的大小(字节),类型为size_t
    (5)重载时,可以带其它参数

    delete 与 delete operator类似

    #include <iostream>
    #include <string>
    using namespace std;
    
    class X
    {
    public:
        X() { cout<<"constructor of X"<<endl; }
        ~X() { cout<<"destructor of X"<<endl;}
    
        void* operator new(size_t size,string str)
        {
            cout<<"operator new size "<<size<<" with string "<<str<<endl;
            return ::operator new(size);//调用全局的operator new申请空间
        }
    
        void operator delete(void* pointee)
        {
            cout<<"operator delete"<<endl;
            ::operator delete(pointee);//调用全局的operator delete释放空间
        }
    private:
        int num;
    };
    
    int main()
    {
        X *px = new("A new class") X;
        delete px;
    
        return 0;
    }
    

      注意::operator new是全局的函数,全局的这样的函数有三个,其中有一个就是placemet new,后面说。一般用户不会(虽然能)在全局重载operator new函数,而是在自定义的类中重载operator new函数。operator new都是申请一个对象的空间内存,所以调用的时候size_t省略,但是对于operator new[]这个函数,要明确的说明数组中对象的个数。


     new和delete操作符我们应该都用过,它们是对堆中的内存进行申请和释放,而这两个都是不能被重载的。要实现不同的内存分配行为,需要重载operator new和operator delete,而不是new和delete。

          operator new就像operator+一样,是可以重载的。但是不能在全局对原型为void operator new(size_t size)这个原型进行重载,一般只能在类中进行重载。如果类中没有重载operator new,那么调用的就是全局的::operator new来完成堆的分配。同理,operator new[]、operator delete、operator delete[]也是可以重载的。

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

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

    1. 缓冲区提前分配,可以使用堆的空间,也可以使用栈的空间。所以分配方式有如下两种:
    class MyClass {…}; 
    char *buf = new char[N * sizeof(MyClass)];
    char buf[N * sizeof(MyClass)];
    
    2. 对象的构造
    MyClass * pClass = new(buf) MyClass; //使用placement new
    
    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用于对象数组的时候:
    char *buf=new char[N*sizeof(MyClass)+sizeof(int)];
    char buf[N*sizeof(MyClass)+sizeof(int)];
    MyClass * pClass=new(buf) MyClass[N]; //使用placement new[]

    前面说到了全局那里有三个operator new函数,下面看看:

    operator new分为三种形式(前2种不调用构造函数,这点区别于new operator):

    1: void* operator new (std::size_t size) throw (std::bad_alloc);
    2: void* operator new (std::size_t size, const std::nothrow_t & nothrow_constant) throw();
    3: void* operator new (std::size_t size, void* ptr) throw(); 

    第一种分配size个字节的存储空间,并进行对象类型进行内存对齐。如果成功,返回一个非空的指针指向首地址。失败抛出bad_alloc异常。

    第二种在分配失败时不抛出异常,它返回一个NULL指针。

    第三种是placement new版本。它不分配内存,调用合适的构造函数在ptr所指的地方构造一个对象,之后返回实参指针ptr。

    三种版本的operator new 定义在全局命名空间,不在std中。第一、第二个版本C++默认在每个编译单元中声明,不需要#include <new>头文件。第一、第二个版本可以被用户更换和重载(一般在类中重载),定义自己的版本,第三种placement new不可重载。

    // operator new example
    #include <iostream>
    #include <new>
    using namespace std;
     
    struct myclass {myclass() {cout <<"myclass constructed
    ";}};
     
    int main () 
    { 
       int * p1 = new int;
    // same as:
    // int * p1 = (int*) operator new (sizeof(int));
     
       int * p2 = new (nothrow) int;
    // same as:
    // int * p2 = (int*) operator new (sizeof(int),nothrow);
     
       myclass * p3 = (myclass*) operator new (sizeof(myclass));
    // (!) not the same as:
    // myclass * p3 = new myclass;
    // (constructor not called by function call, even for non-POD types)
     
       new (p3) myclass;   // calls constructor
    // same as:
    // operator new (sizeof(myclass),p3)
     
       return 0;
    }
    //结果输出:myclass constructed
    

      

  • 相关阅读:
    CF终于上紫了。。。
    CF567F/51nod2522 上下序列
    bzoj 前100题计划
    CF1110G Tree-Tac-Toe 博弈论、构造
    BZOJ4816 SDOI2017 数字表格 莫比乌斯反演
    UOJ400/LOJ2553 CTSC2018 暴力写挂 边分治、虚树
    Luogu4774 NOI2018 屠龙勇士 ExCRT
    CF1039D You Are Given a Tree 根号分治、二分、贪心
    CF1056E Check Transcription 字符串哈希
    Luogu4345 SHOI2015 超能粒子炮·改 Lucas、数位DP
  • 原文地址:https://www.cnblogs.com/stemon/p/4834585.html
Copyright © 2011-2022 走看看