前面我已经非常详细地写了关于new
和delete
的底层实现原理,这里就讲一下比较轻松的,如何合理地去使用new
和delete
和为什么要如此。
如果看完之后还有兴趣继续探究,可以去看我前面的博客——【C++】 深入探究 new 和 delete
我们都知道,用new
操作符分配单个对象的内存和分配数组对象的方式是不同的,同样,delete
操作符针对不同new
方式分配的对象也要采用不同的delete
方式。
为什么成对使用new
和delete
要采取相同形式?
首先,观察如下代码:
class A
{
private:
int value;
public:
A(int v):value(v) {};
A() {
cout << "creat A" << endl;
}
~A() {
cout << "delete A" << endl;
};
};
void test()
{
A *lis = new A[3];
delete lis;
}
输出结果如下:
creat A
creat A
creat A
delete A
我们使用new []
操作符动态生成了三个A对象。然后用delete
操作符释放内存
可以从输出看到,三个A对象的默认构造函数都已经成功调用,说明三个A对象都已经成功的分配了内存空间。
但是当只使用delete
去进行空间释放的时候,只有一个对象被析构了。说明了什么?
delete
操作符认为lis
指针指向的只是(only)一个A对象,所以只调用了第一个A对象的析构函数,然后将第一个对象A的内存进行释放,返还给操作系统
后面的内存空间有没有被释放?我想肯定是有的,但是编译器会认为这样会导致内存泄漏,所以报错。
为什么?
因为当你new[]
出来的对象含有显式的析构函数时,当你使用完这些对象,用delete[]
去释放的时候的话,操作系统不仅会释放你new[]
出来的那块内存空间还会对每个对象执行析构函数。
因为你可能在析构函数中写了一些释放内存的操作,但如果你没有用
delete[]
进行空间释放的话,除了第一个,后面的析构函数都没有被执行,编译器就会认为这样的不配对行为会导致内存泄漏。
针对复杂类型(含有默认构造函数、显式析构函数的对象)
当使用new[]
创建对象数组,然而使用delete
操作符进行内存释放的时候,最大的问题就是:即将被释放的内存中到底存在多少个对象?
这个问题也能简单点:即将被释放的那个指针,所指的到底是单一对象,还是对象数组?
因为单一对象的内存布局和数组的内存布局是不同的。更明确的说,数组所用的内存中还包括了“数组大小”(多少个对象)的记录,以便delete
知道需要调用多少次析构函数。
单一对象的内存中就没有这个记录。
你可以把两者的内存布局想象成如下:
单一对象:object
对象数组: n
object
object
object
…
当然编译器不一定就是这么实现的。我们可以验证一下:
class A
{
private:
int value;
public:
A(int v):value(v) {};
A() {
cout << "creat A" << endl;
}
~A() {
cout << "delete A" << endl;
};
};
void test()
{
A* lis = new A[3];
int *p = (int*)lis;
cout << *(p - 1) << endl;
}
输出如下:
creat A
creat A
creat A
3
果然,面对复杂类型数组,其前面存在这样的4个字节存储长度信息。
当你对着一个指针使用delete
的时候,唯一能让delete
知道其所要处理的内存是否存在一个数组大小记录的方法就是:由你自己告诉它
如果你使用delete
的时候加上中括号,则delete
认为这个指针指向的是一个对象数组,否则delete就认为这仅仅是一个对象。
string* strList = new string[50];
string* strSingle = new string();
......
delete[] strList;//删除对象数组
delete strSingle;//删除一个对象
我们有必要认真地执行这样的配对,因为如果不这样操作的话,对于复杂类型(含有默认构造和显式析构函数)会产生未定义的行为,导致内存泄漏和奇怪的错误。
typedef数组的时候使用new和delete导致的奇怪行为
考虑如下的typedef
:
typedef string strList[10];//10个string的列表
string * lis = new strList;
//new strlist 返回一个string*,就像new string[4]一样
delete lis;//未定义行为
delete[] lis;//正确
为避免这样的问题,尽量不要对数组的形式做typedef
动作,如果一定想要这样使用,typedef
的作者必须将这个typedef
的 new
和delete
的使用方式进行详细描述。
针对简单类型
相信大家对为什么简单类型和内置类型为何可以进行下面这种操作而不导致内存泄漏和错误觉得很费解:
void test()
{
int* lis = new int[100];
delete lis;
}
或者只有简单数据的结构体:
struct node
{
int a;
int b;
int *c;
};
void test()
{
node* lis = new node[10];
delete lis;
}
这样都是可以的,为什么?
想了解的可以进入我的这个博客进行了解:【C++】new和delete面对简单类型时可以不成对使用的原因