关于C++ 的operator new、 operator new[]、operator delete、operator delete[]、placement new、placement delete

本文深入探讨C++中operatornew与new运算符的区别,operatornew与operatordelete的各种重载版本,及何时与如何重载这些运算符。同时,文章介绍了placementnew的概念与应用场景,帮助读者理解C++内存管理的细节。

1.operator new 与 new operator的区别

答:我们经常会使用new来动态分配一个对象,而使用的new就是new运算符。那么new一个对象后面会发生什么事呢?

     ① 调用operator new函数申请一段内存(大小刚好就是你要new的那个对象的大小,那个大小会作为参数传给函数,如果是operator new[]则是所有对象总共的大小)

     ② 然后编译器会在那段内存上调用构造函数初始化对象

     ③ 然后返回这段内存

需要注意的是当我们new一个带有构造函数的类对象时是会调用构造函数的,而当我们直接去调用那个operator new函数去申请内存,则得到的只是一段raw memory。函数内部其实就是间接调用了malloc函数。

2.operator new与operator delete种类

标准库定义了operator new和operator delete的8个重载版本:

//这些可能会抛出异常

 vod* operator new(size_t);

void* operator new[](size_t);

void* operator delete(void*)noexcept; 

void* operator delete[](void*)noexcept;

//这些版本承诺不会抛出异常

 vod* operator new(size_t,nothrow_t&)noexcept;

void* operator new[](size_t,nothrow_t&)noexcept;

void* operator delete(void*,nothrow_t&)noexcept; 

void* operator delete[](void*,nothrow_t&)noexcept;

nothrow_t是一个定义在new头文件中的空struct(不包含任何成员),new文件中还定义了一个名为nothrow的const对象

,用户可以通过这个对象来指定非抛出异常版本。与析构函数类似,operator delete也不允许抛出异常,当我们重载这些运算符时必须指定其不抛出异常。

3.重载operator new和operator delete

 答:如果我们需要重载这两个函数的话,我们可以定义在全局或者作为类的成员函数。而且当作为类的成员函数时默认是静态的,我们不需要显式声明。而当编译器发现一条new表达式或者delete表达式之后,它将在程序中寻找可供调用的operator new函数。如果被分配的对象是类类型的话,会先去类的内部或者基类中查找,如果该类有operator new和operator delete成员的话就调用它们,如果没有查找到再去全局找。如果全局有用户自定义的重载版本的话就调用它们,如果没找到的话就调用标准库的版本。我们还可以直接使用作用域说明符直接声明我们要调用的就是全局版本的例如::new只在全局找。

      当我们定义operator delete或者operator delete[]作为成员时,该函数还可以包含另外一个参数size_t的形参。此时,该形参的初始值是第一个形参所指的对象的字节数。size_t形参可用于删除继承体系中的对象。如果基类有一个虚析构函数(当这个类用于多态时),则传递给operator delete的字节数将因待删除指针所指对象的动态类型不同而有所区别。举个例子:

   

class base{

public:
    void* operator new(size_t s){
     ::operator new(s);
}
    void operator delete(void* memory,size_t s)
    {
        ::operator delete(memory);
     }
    virtual ~base(){}
}
class dreied:public base{

   
}
int main()
{
    base* b=new dreied();
    delete b;
    //此时当我们使用delete时,我们需要根据b实际指向的对象来调用实际对象的析构函数并且告知释放的内存大小传给形参s 
   //所以这个s的大小我们不能直接就认为它等于base的大小
}

4.那么什么时候需要重载这些函数呢?

答:《Effective C++》条款50替我们总结了一些:

 

5.编写operator new与operator delete需要注意的地方

  ① 首先是对0字节内存申请的处理

  ② operator new需要一个死循环,当正确调用operator new得到的是一片内存并跳出循环,如果失败的话 需要有一个处理函数去不断获取内存,如果获取不到内存的话可以在执行过程中替换其他的处理函数,如果还是分配不到,那就抛出bad_alloc异常。

  ③ 当我们为一个基类制作内存分配器时,我们并没有为派生类准备。所以当派生类继承了基类的operator new时,它的行为是刚好为基类适配的,派生类是不会享受到的。所以我们需要一个判断 if(size!=sizeof(base)) return ::operator new(size);

当我们使用基类定义的operator new去申请一个派生类对象时,我们改为使用全局的operator new。

  ④ 当我们自定义operator delete时需要对空指针做处理(其实就是直接返回,什么都不做)

  ⑤ 参考问题3的末尾一段

6.placement new与placement delete

答:placement new是一种可以在已分配的内存上创建对象的方式(这块内存甚至可以不再堆上),在《effective C++》中讲到了如果operator new除了一定会有的那个size_t参数之外还有其他的这便是一个所谓的placement new。而众多placement new版本中特别有用的一个是“接受一个指针指向对象被构造之处”,那样的operator new长相如下:

     void* operator new(std::size_t,void* pMemory )throw();

这个版本的new已经纳入了C++标准程序库,你只要#include<new>就可以取用它,它可以在已经存在的堆内存上建立对象。

如果placement new在原先已经存在的一个对象上构造新的对象,原先那个对象的析构函数并不会调用,唯一会使他调用的方式就是delete那个指针,但是那样的话我们就无法继续使用那块内存了。所以最好的方式是通过指针主动去调用析构函数,然后再placement new在那块内存上,所以如果我们无法确定那块内存是否有一个对象的话,约定的习俗就是先析构它再placement new在上面。(C++标准库已经有个一个placement delete替我们做了析构的活,所以不用我们主动去调用了)

这里我想再补充一个《Effective C++》上面的问题:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值