作用
- 销毁先前由new表达式分配的对象
语法
::(可选) delete 表达式 | (1) |
::(可选) delete [] 表达式 | (2) |
- 销毁 new 表达式创建的单个非数组对象
- 销毁 new[] 表达式创建的数组
对于第一种(非数组)形式:
- 表达式 必须是如下两种情况之一:
- 指向对象类型的指针
- 可以按照语境隐式转换到这种指针的类类型
- 值 必须是如下三种情况之一:
- 必须为空(null)
- 或指向new表达式所创建的非数组对象的指针
- 或指向 new 表达式所创建的对象的基类子对象的指针
若 表达式 为其他值,包括它是通过new 表达式的数组形式获得的指针的情况,其行为未定义。
对于第二种(数组)形式,
- 表达式 必须是如下两种情况之一:
- 空指针值
- 先前由 new 表达式的数组形式所获得的指针值
若 表达式 为其他值,包括若它是由 new 表达式的非数组形式获得的指针的情况,其行为未定义。
表达式的结果始终具有 void 类型。
也就是说:调用new申请的内存用delete释放;调用new[]申请的内存就一定要用delete[]释放。
我们来看个例子:
- 考虑有如下类定义:
class Test{
public:
Test();
~Test();
};
- 当我们在堆中创建一个新对象时,可以用如下代码
Test* pTest = new Test;
- 当我们要释放这个对象时可以使用以下代码:
delete pTest;
- 当我们要在堆中创建10个对象是可以使用以下代码:
Test* pTests = new Test[10];
- 当我们要释放这10个对象时,与new[]相呼应,必须使用
delete[]
两者的区别在于带[]
的new []
和delete[]
会对每个元素调用构造函数和习惯函数
注解
不能删除指向void的指针,因为它不是指向完整对象类型的指针。
自c++11起,因为关键词 delete 之后的一对方括号始终被解释为 delete 的数组形式,所以紧跟在 delete 之后的拥有空俘获列表的 lambda 表达式必须以括号括起来。
// delete []{return new int; }(); // 解析错误
delete ([]{return new int; })(); // OK
问题
为什么在调用delete[]时,括号中不需要指定元素的个数
据说初期的C++在调用delete[]
时是需要指定元素个数的,但是那样会非常麻烦。所以后来进行了改进:在实现new[]的时候同时申请内存空间保存元素的个数。基本上是这个样子:
struct array {
size_t count_of_test;
Test t[10];
};
在调用new[]
时先通过malloc
申请内存多申请一点空间保存count_of_test
,然后是返回给用户t空间的例子。这个地址要在malloc获得的array地址上加一个偏移量。最后对这个t空间调用每个元素的构造函数。
调用delete[]的参数实际上是t空间的地址。先对这个地址参数进行减偏移量运算得到array地址并取得count_of_test,然后对t空间调用析构函数count_of_test次。最后以array地址作为参数调用free()函数。
除了正确调用构造函数/析构函数以外,还要处理保存元素个数的空间。
所以必须配对使用。
其他
为了避免内存泄漏,每个动态内存分配必须与一个等同相反的deallocation对应。函数operator delete与delete操作符的关系与operator new与new操作符的关系一样。当:
string *ps;
.....
delete ps;
operator delete调用:
void operator delete(void *memoryToBeDeallocated);
导致编译器生成(编译器会生成代码来析构对象并释放对象的内存。):
ps->~string();
operator delete(ps);
这又一个隐含的意思,如果你只想处理未被初始化的内存,你应该绕过new和delete操作符,而调用operator new获得内存和operator delete释放内存给系统:
void *buffer = operator new(50 * size(char)); //分配足够的内存以容纳50个char,没有调用构造函数
...
operator delete(buffer); //释放内存,没有调用析构函数
这与C中调用malloc和free等同。
如果你用placement new在内存中建立对象,你应该避免在该内存中用delete操作符。因为delete操作符调用operator delete来释放内存,但是包含对象的内存最初不是使用operator new分配的,placement new只是返回传递给它的指针。你应该显式调用对象的析构函数来解除构造函数的影响:
class Widget{
public:
Widget(int widgetSize);
};
Widget * constructWidgetInBuffer(void *buffer, int widgetSize){
return new(buffer)Widget(widgetSize);
}
// 在共享内存中分配和释放内存的函数
void *mallocShared(size_t size);
void freeShared(void *memory);
void *sharedMemory = mallocShared(sizeof(Widget));
Widget *pw = constructWidgetInBuffer(sharedMemory , 10);
//...
//delete pw; //结果不确定!共享内存来自mallocShared,不是operator new
pw->~Widget(): //正确,析构pw指向的Widget,但是不释放包含Widget的内存
freeShared(pw); //正确,释放pw指向的共享内存但是没有调用析构函数