C++内存管理:其三、new和delete的行为拆分

本文详细介绍了C++中的new、delete以及它们的底层行为,包括自己实现new和delete功能,operatornew和operatordelete的重载,placementnew的概念、优势和使用方法。特别强调了placementnew在内存优化和进程间通信中的应用。
摘要由CSDN通过智能技术生成

new和delete都是C++的关键字,不可重载。其底层的行为可以看作多个函数的组合。

一、自己实现new与delete的功能

#include <iostream>
using namespace std;

class Student{
private:
    int age{24};
public:
    Student(){
        cout<<"start"<<endl;
    }

    ~Student(){
        cout<<"end"<<endl;
    }

    void f(){
        cout<<"age = "<<age<<endl;
    }
};

int main(void) {
    Student * p = (Student *)operator new(sizeof(Student));    //自己实现new
    new(p) Student;

    p->f();

    p->~Student();          //自己实现delete
    operator delete(p);

    return 0;
}

第一行:
Student * p = (Student *)operator new(sizeof(Student));
operator new是C++自带的函数,可以重载。准确调用方法是:
::operator new(sizeof(Student));
::表示全局命名空间,注意不是std::标准命名空间!
底层调用的是malloc函数,实际上返回的是void * 指针。参数表示要申请的字节数。

第二行:
new§ Student;
表示在给定的地址(堆上地址)执行构造函数。

对应delete的操作:
p->~Student();表示在某个地址上执行析构函数。
operator delete§;
调用的是C++自带的函数,同样可以重载。底层调用的是free()函数。

二、operator new和operator delete重载

operator new和operator delete是全局空间里面的两个函数,前者用于申请空间,后者用于释放空间。操作符new的行为可以拆解为三步,第一步就是调用operator new()函数申请内存空间。对于operator new()函数,我们可以重载自己的版本。
operator new()函数重载规则如下:
(1)可以重载全局版本,但是非常不推荐,会影响所有类的new操作。可以重载某个类的版本。重载的时候将operator new定义为成员函数即可。operator new函数默认是静态函数,即使你不加static,编译器也会将其置为static函数。因为operator new的作用是构造对象,调用它时候一般而言还没有将对象构造出来,如果是非static函数就不可用了。
(2)operator new的标准格式为void* operator new(size_t size);
重载的时候也可以定义多个参数,但是第一个参数必须是size_t size。
(3)当用户对这个类进行new操作的时候,会自动调用operator new函数。划重点:
一般new是不带参数的,但是如果重载的operator new版本有多个参数,那么new中需要传入除了size_t size外的其它参数。例如:
void* operator new(size_t size, int test);
那么new的时候:new(123) ClassName;123就是传给形参test的值。
(4)系统自带两个重载的operator new函数,如果我们在类中重载了自定义版本,那么这两个都会被禁用!!!我们new的时候只会调用我们自定义的operator new函数。

看代码:

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

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

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

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

    long long a=1;
};

int main()
{
    Foo *px = new Foo;
    delete px;

    Foo * ps = new Foo[3];
    delete []ps;
}

运行结果:

8
operator new
operator delete
24
operator new []
operator delete []

我们同样可以重载operator delete 、operator new[] 、operator delete []

三、operator new的多版本重载

operator new可以重载多个版本,但是注意,所有版本的第一个参数都必须是无符号整数!我们通过new关键字进行使用的时候,不能传入第一个参数,从第二个参数开始传入。看代码:

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

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

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

    long long a=1;
};

int main()
{
    Foo *p1 = new(123) Foo;    //调用的是void* operator new(std::size_t size, int test)
    delete p1;

    Foo *p2 = new("name") Foo;  //调用的是void* operator new(std::size_t size, string test)
    delete p2;
}

运行结果:

size = 8 test =  123
operator new
size = 8 test =  name
operator new

这个特性非常非常有用,可以很好地解释Placement new.

四、Placement new

Placement new,顾名思义,就是在指定位置new一个对象出来。placement new 是重载operator new 的一个标准、全局的版本。
请记住一句话:Placement new就是operator new函数!!!
函数定义如下:

_GLIBCXX_NODISCARD inline void* operator new(std::size_t, void* __p) _GLIBCXX_USE_NOEXCEPT
{ return __p; }

第一个参数size_t完全被忽略了,而且完全没有malloc的过程,上层传入了一个指针,原封不动地返回这个指针。后面的行为是这样的:编译器根据operator new返回的指针,在这个地址上执行构造函数,从而达到了定点new的目的。这个指针未必指向堆,也可以指向栈!!!

Placement new 的优势

(1)用placement new 避免频繁申请内存,一块内存可以多次利用。

(2)可以提高构建对象的效率。如果堆中空间不足,可以将对象构建在我们事先准备的缓冲区中。

(3)配合共享内存使用,可以在进程间传递对象!!!这个简直是IPC的大神器!!!

五、Placement new的使用

看代码:

class Foo {
public:
    Foo(){
        cout<<"start!!!"<<endl;
    }

    ~Foo(){
        cout<<"end!!!"<<endl;
    }
    long long a=123;
};

int main()
{
    char * buff = new char [sizeof(Foo)];     //在堆上定点new
    Foo *p1 = new(buff) Foo;
    cout<<"a = "<<p1->a<<endl;
    //delete p1;  有风险,这一块内存实际上不被这个对象独占
    p1->~Foo();

    delete [] buff;
    cout<<endl;


    char buf[sizeof(Foo)];      //在栈上定点new
    Foo *p2 = new(buf) Foo;
    cout<<"a = "<<p2->a<<endl;
    p2->~Foo();

    return 0;
}

使用步骤如下:
(1)申请内存,这个内存可能是在堆上也可能是在栈上。
(2)在给定的地址上定点new。
(3)使用这个对象。
(4)执行析构函数。

注意点如下:
(1)使用Placement new的时候,最好不要自己重载operator new函数,因为一旦有了自己重载的版本,标准库的版本就会被禁用。当然如果自己重载一个和标准库中的inline void* operator new(std::size_t, void* __p);函数一模一样的,也是可以的。
(2)用户不被允许自己调用构造函数,但是被允许自己调用析构函数。
(3)定点构造的对象使用完以后,不要直接delete,因为这块内存可能会被用来构造其他对象,只能手动调用析构函数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值