17.new与delete成对使用时必须形式相同

问题实例

1
2
string * stringarray = new string[100];
delete stringarray;

在上述代码中我们成对地使用了newdelete,似乎避开了资源泄露,但其实并非如此。
上文动态分配了100个string对象,却只删除了第一个,其它string对象的析构函数根本没有被调用。


new与delete

调用new与delete时发生了什么?

当你使用new的时候,有两件事会发生:

  1. 内存通过operator new被分配出来(new操作符详见Effective C++ 50 52)
  2. 针对此内存有一个或多个构造函数被调用

对应的,使用delete的时候也会发生两件事:

  1. 一个或多个析构函数被调用
  2. 通过operator delete释放内存

delete如何判定对象个数?

delete的最大问题在于:即将被删除的内存之内究竟存有多少对象?这决定了会调用多少次析构函数。换句话说,delete需要明确:被删除的指针究竟指向单一对象还是对象数组
在编译器的具体实现中,单一对象的内存布局不同于数组的内存布局,数组的内存布局中记录了数组大小,这得以让delete函数知道需要调用多少次析构函数。它们的内存布局大概是这样:
image_1cap38n6l1a205v11tnn1t9017gu9.png-11.9kB

delete的正确使用

我们通过人为指定的方式让delete明确其操作:使用delete或delete[];
前者认定当前指向的是单一对象,后者则认定当前指向数组:

1
2
3
4
string* pstr1 = new string
string* pstr2 = new string[100];
delete pstr1;
delete[] pstr2;

误用delete形式的后果

如果我们对pstr1使用delete[],结果未定义,可想而知它会误认为当前指向某一个对象数组,然后读取某块内存将其解释为数组大小,然后反复调用析构函数。

如果我们对pstr2没有使用delete[],结果亦未有定义,但肯定调用的析构函数不足。事实上,即使针对没有析构函数的内置类型,这种写法也是有害的。

new、delete与typedef

本节的规则十分简单,无非是成对使用new与delete时必须保证形式一致,读者想必会认为稍加注意不难做到。但对于某些重度typedef爱好者而言,还是要多加留心为是:

1
2
3
4
typedef string AddressLines[4];
string* pal = new AddressLines;//pal指向的是数组对象
delete pal;//error!
delete[] pal;

我认为C++语言应当尽量减少对数组使用typedef定义,在降低了可读性的同时还容易引发错误。在能够使用STL容器的地方坚决不使用动态数组。