0. placement new

原地构造
用法:

int *p = new int(10);  // new operator
new(p) int(1000);   // 在 p 指向的内存上 构造一个int对象,即placement new
// 此时 *P 为 1000。

因为是在一个已有的空间上构造对象,所以,空间的释放,就由free或者delete负责了。

0.1 重载placement new

与后边的 3 :operator new的重载无异,就是可以多增加几个函数,但是,第一个参数必须是std::size_t 类型,这个参数接收的是 new 后边类型的大小,不用显式传参,默认会传递。
重载示例:

#include <iostream>

using std::cout, std::endl;

class base{

public:
    base(){
        cout << "ctor" << endl;
    }

    ~base(){
        cout << "dtor" << endl;
    }

    // 标准库中的 placement new, 这里什么都没做,只是返回原来的指针。
    void * operator new(std::size_t size, void *p){
        cout << "std placement new" << endl;
        return p;
    }

    //重载 placement new,附加了一个参数 a,参数size会默认传递,不用显式传参。
    void * operator new(std::size_t size, int a){
        cout << "a = " << a << endl;
        return malloc(size);
    }

    void * operator new(std::size_t size){
        cout << "new diy" << endl;
        return malloc(size);    
    }

    void operator delete(void *p){
        cout << "delete diy" << endl;
        free(p);
    }

private:
    int a, b, c;
};


int main(){
    base p;
    new(&p) base;
    new(4) base;

    return 0;
}

运行结果:

ctor  // 对象默认的构造函数输出的
std placement new  // 默认placement new 输出的
ctor // 默认placement new 输出的
a = 4 // 重载的placement new 输出的
ctor  // 重载的placement new 输出的
dtor  // 析构

1. new operator

一般情况下,我们使用的new都是new operator
用法:

int *p = new int(3);

其实底层有两个过程:
第一个过程:开辟空间
第二个过程:构造对象

2. delete operator

delete operator就是平时常用的delete,与new 搭配成对使用。
用法:

int *p = new int;
delete p;

3. operator new

是全局的库函数。

用法:

void* operator new(size_t size);

其实,operator new底层调用的是malloc(调用形式也挺像的,传入一个开辟的Byte数,返回一个void *)。

  • 注意,operator new 支持重载!当然了,如果重载了,那就要重载对应的operator delete

4. operator delete

是全局的库函数,与operator new搭配使用。

用法:

int *p = new int;
operator delete(p);

底层调用的是free

  • 支持重载。

5. new 做了哪些事情?

new 操作可以大致的认为包含了两个过程:
第一个过程:开辟相应的空间(使用operator new)。
第二个过程:在刚开辟的空间内构造一个对象(placement new)。

模拟new操作。

int *p = static_cast<int*>( operator new(sizeof(int)) );   // new operator 开辟空间
new(p) int(100);  // placement new 在p指向的内存上构造一个对象

上边说到operator newoperator delete支持重载,重载可以为我们自己的类类型定制化的分配内存。
只需要在类内重载operator newoperator delete

编译器遇到new和delete时,首先看对应的类类型中是否有重载的operator newoperator delete,如果有,则优先调用重载的版本去分配内存,然后再原地构造。如果没有重载版本,就调用全局的库函数。

如果一个类内有重载的operator newoperator delete,会优先调用重载版本,代码如下:

#include <iostream>

using std::cout, std::endl;

class base{

public:
    base(){
        cout << "ctor" << endl;
    }

    ~base(){
        cout << "dtor" << endl;
    }


    void * operator new(std::size_t size){
        cout << "new diy" << endl;
        void *resPtr = malloc(size);
        return resPtr;        
    }

    void operator delete(void *p){
        cout << "delete diy" << endl;
        free(p);
    }

private:
    int a, b, c;
};


int main(){

    base *p = new base;

    delete p;

    return 0;
}

运行结果:

new diy
ctor
dtor
delete diy

也可以显式的使用全局的库函数来new一个对象出来,把main函数中的new和delete换成全局的库函数,如下:

base *p = ::new base;
::delete p;

运行结果:

ctor
dtor

显式的调用了全局的库函数,而没有调用类类型重载的版本。

当然,如果不在类内进行重载,在全局进行重载,也是可以的。

这个两个函数,在类内重载,必须是静态的,即有static修饰,因为调用这两个方法时,对象还没创建(调用new时)或者已经析构(调用delete时),this指针此时是无效的,再想去通过this调用一个方法,这是不可能的,所以必须是静态的。

上边的代码中,没有加static修饰,也可以完成操作,是因为编译器替我们加上了static

6. array new 与 array delete

int *p = new int[10];
delete[] p;

array new即是用new申请一个数组空间,此时 p 指向第一个元素的起始位置,释放时必须使用对应的array delete,也就是delete[ ]

对于基本数据类型来说,如果把上边的delete[] p 改成 delete p,没什么问题,不会造成内存泄漏。但如果是自定义的类,并且类中有在堆上开辟内存空间的话,就会造成内存泄漏。
因为delete p 只会调用一个元素的析构函数,然后释放对应的空间,数组中的其余元素,虽然元素(类对象)本身的空间释放了,但是该对象原本来堆上开辟的内存空间,并没有释放掉(因为他的析构函数没有被调用),由此造成内存泄漏。

与上边的new 和 delete一样,array版本的new和delete也支持重载。
示例如下:

#include <iostream>

using std::cout, std::endl;

class base{

public:
    base(){
        cout << "ctor" << endl;
    }

    ~base(){
        cout << "dtor" << endl;
    }

    void * operator new[](std::size_t size){
        cout << "new[] diy" << endl;
        void *resPtr = malloc(size);
        return resPtr;        
    }

    void operator delete[](void *p){
        cout << "delete[] diy" << endl;
        free(p);
    }

private:
    int a, b, c;
};


int main(){
    base *p = new base[2];
    delete[] p;
    return 0;
}

运行结果:

new[] diy
ctor
ctor
dtor
dtor
delete[] diy

7. delete[ ] 为什么能够释放数组?

在使用new创建一个数组时,所开辟的实际内存大小比要申请的大小要大一些,在申请到的数组空间的上下都有额外的开销,来记录这块数组空间的相关信息,其中就包括数组空间的长度,每个元素的长度。所以 delete[ ] 可以顺利的知道数组的长度,并且可以成功的调用所有元素的析构函数。