C++中各种new/delete总结

本文详细介绍了C++中new、delete运算符的功能与限制,以及operator new/delete的重载和placement new的概念、特性及使用场景。通过对这三种内存管理方式的理解,有助于提升C++程序员对内存操作的掌控力。
摘要由CSDN通过智能技术生成

C++语言提供了三种关于new、delete的操作或者概念,这三种分别体现了C++语言对内存进行操作的不同层次,深入理解这三种情形,对于不论是个人开发中对内存进行管理或者是阅读其他代码都是一个基础。特在此进行总结。
凡是涉及到对内存进行操作的时候,如果新开始配置,那么对其进行细化都可以分为三个步骤:
1. 分配一段原始内存,其上可以存储任意类型的数据对象(也就是可以理解为对应的指针类型是void * );
2. 构造一个指定类型的对象,不论是语言原生支持的类型还是用户自定义类型,都会调用构造函数进行该对象的构造;
3. 将构造的对象存储到分配的原始内存上,让指向改内存的指针类型从void * 变为相应对象类型的指针类型。
同理,如果是使用完成进行释放内存,那么细化的步骤与上述相反:
1调用对象的析构函数销毁这个对象;
2将堆上使用的内存释放归还给操作系统。

new/delete operator

new、delete运算符,从字面上看,这是一种语言提供的特殊运算符,与“+、-、*、/”这种算术运算符属于同一个范畴,也就是C++语言规范提供的,开发者是不能进行改动的,只能被动的使用。

功能

new operator实现上述提到的所有三个步骤:从堆上扫描可使用的内存进行分配,调用相应类型的构造函数构造一个对象,最后将对象存储到相应的堆内存上。delete operator同样实现了上述所有细化的步骤。

限制

  • new和delete操作符是语言支持的通用内存操作符,无法进行重载也就无法修改其行为,因此,如果希望对内存管理的细化的步骤进行操作,就无法实现。
  • 语言支持的通用行为效率较低,对于频繁内存申请与释放的程序不适用。

operator new/delete

operator new/delete中的operator与new/delete就是C++语言提供的运算符重载语法,与“operator ++,operator ==”这种运算符重载是同一个范畴,二者合在一起作为运算符重载的函数名称。

功能

以自定义类的一个运算符重载成员函数出现,实现自定义额外功能后,一般通过调用全局的operator new函数,来为所属类的对象分配指定大小的内存,因为是类的一个成员函数,因此不会调用类的构造函数。
进行内存分配时,如果无法满足要求会有如下行为:
(1)如果有new_handler就调用之,否则
(2)如果没有要求不抛出异常(没有使用nothrow表达式),就执行bad_alloc异常,否则
(3)返回0

特性

如果需要对某一个自定义的类的内存分配做出个性化的设置,就可以利用这个operator new/delete运算符重载函数来重写,需要注意的是:
(1)operator new重载函数的返回值必须声明为void *,operator delete的返回值必须声明为void;
(2)operator delete的参数必须为通过operator new分配的内存对应的指针,且要转换为void * 类型后调用;
(3)operator new的第一个参数必须为请求分配的空间的字节大小,类型为size_t。
全局的operator new和operator delete可以看做是malloc和free函数,但是它们不能交叉使用,因为他们进行内存登记的方式不同。一个类的operator new和operator delete可以看做是申请该类对象的内存分配方法。

#include <iostream>
#include <string>
using namespace std;

class A
{
public:
  A(){cout << "Constructor called" << endl;}
  ~A(){cout << "Destructor called" << endl;}

  void * operator new(size_t s, string str)
  {
    cout << "operator new: size " << s << ", other param " << str << endl;
    return ::operator new(s);
  }

  void operator delete(void * p)
  {
    cout << "Operator delete called" << endl;
    return ::operator delete(p);
  }
};

int main(int argc, char * argv[])
{
  A *pa = new ("This the other param string") A;
  delete pa;
  return 0;
}

运行结果:

user@ubuntu:~$ g++ -o newdelete newdelete.cpp
user@ubuntu:~$ ./newdelete
operator new: size 1, other param This the other param string
Constructor called
Destructor called
Operator delete called

上面实例中,通过为自定义的类型A定义operator new 和operator delete函数,在主函数使用普通的new、delete运算符时,会自动调用类A的operator new和operator delete函数,与此同时,主函数中的new和delete还会去调用类A的构造函数和析构函数。
其中,如果只是使用new A这样申请一个对象,那么会传到operator new函数的第一个参数,如果是申请一个数组,那么对应的字节数也会自动传递过去。这也是operator new的第一个参数必须为申请内存的大小的原因。

placement new

placement new是operator new的一个标准重载函数,不够被自定义的版本替换。也就是operator new全局函数的一个特殊重载版本,根据重载函数的含义,placement new的参数是确定的两个参数,与全局的operator new仅一个size参数和自定义operator new任意参数不同。函数定义如下:

void * operator new(size_t, void *p) throw() {return p;}

其忽略了第一个参数size,只返回第二个参数,也就是没有使用第一个size参数去在堆中申请内存,而是只调用构造函数,并绑定到传入的第二个预先配置好的内存的指针参数上。
由于多了第二个参数,因此在程序中使用时需要指定这个参数,就与上述例子中传递一个string的额外参数类似。标准使用步骤如下:

char * buf = new char[sizeof(B)];
B *pb = new (buf) B();
pb->~B();
delete []buf;

功能

根据前面的分析,这个函数的主要功能是构建一个对象并放置到指定的内存位置上,也就是实现了前面所述的第二和第三个步骤。这里面通过忽略第一个分配内存大小参数跳过内存的分配,在已经分配好的内存上构造对象就是placement new最大的优势。

特性

所有类的析构函数都可以系统自动调用,但是唯有使用placement new构造的对象需要显式调用析构函数。
placement new调用构造函数之后将对象绑定到传入的指针指向的内存中,这里并没有对内存的类型做限制,也就是既可以是栈、也可以是堆。但是使用operator new或者new运算符都是只能在系统的堆上进行内存分配的。

存在的理由

1. 用placement new 解决buffer的问题
问题描述:用new分配的数组缓冲时,由于调用了默认构造函数,因此执行效率上不佳。若没有默认构造函数则会发生编译时错误。如果你想在预分配的内存上创建对象,用缺省的new操作符是行不通的。要解决这个问题,你可以用placement new构造。它允许你构造一个新对象到预分配的内存上。
2. 增大时空效率的问题
使用new操作符分配内存需要在堆中查找足够大的剩余空间,显然这个操作速度是很慢的,而且有可能出现无法分配内存的异常(空间不够)。placement new就可以解决这个问题。我们构造对象都是在一个预先准备好了的内存缓冲区中进行,不需要查找内存,内存分配的时间是常数;而且不会出现在程序运行中途出现内存不足的异常。所以,placement new非常适合那些对时间要求比较高,长时间运行不希望被打断的应用程序。

使用步骤

1 预分配缓存的内存
可以使用堆或者栈均可,或者直接地址分配:
(1)普通的new在堆上开辟缓存区

class B;
char *buf = new char[sizeof(B)]; //new char[sizeof(B) * N]

(2)使用栈,自动变量声明

class B;
char buf[sizeof(B)]; //buf[N * sizeof(B)]

(3)直接使用有效的内存地址分配

void * buf = reinterpret_cast<void*>(0x00004072);

2 调用placement new进行对象构造和绑定

B *pb = new (buf) B(); //new (buf) B(a, b)

3 正常使用该对象

pb->method1(...);
pb->method2(...);
pb->method3(...);
...

4 显式调用析构函数

pb->~B();

5 销毁缓存区
缓冲区可以反复使用,这也是使用placement new比普通的new更快的原因,当使用完成,不需要缓存区时,就需要根据当时申请这个缓存区的方式进行释放。

//用new在堆上申请的,需要显式释放
delete []buf;

//在栈上分配的和直接地址转换的程序会自动回收,不需要显式释放。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值