第13条:
互相联系的
new
和
delete
要使用同样的形式
请观察下面的代码有什么不妥之处:
std::string *stringArray = new std::string[100];
...
delete stringArray;
一切似乎都按部就班,
new
语句与
delete
相匹配。然而,这却是十分错误的。这段程序将出现无法预知的行为。最起码的是,由于该
stringArray
所指向的
100
个
string
对象中的
99
个没有被析构函数所析构,它们将很有可能得不到销毁。
当你使用了一个
new
语句时(也可以说,使用
new
动态创建了一个对象),将会发生两件事情。第一,分配内存(通过一个名为
operator new
的函数,参见第
49
和第
51
条)。第二,为这段内存调用一个或多个构造函数。当你使用了一个
delete
语句时,将会发生另外两件事情:第一,为分配的内存调用一个或多个析构函数。第二,释放内存(通过
operator delete
函数实现,参见第
51
条)。
delete
的关键问题是:内存中存在多少需要删除的对象呢?答案取决于需要调用多少析构函数。
实际上,答案十分简单,那就是:指针是指向一个单独的对象,还是一组对象?这个问题很关键,因为为单个对象分配的内存与为一系列对象分配的内存在形式上有本质的不同。具体地说,为数组分配的内存通常要保存数组的大小,这就使得
delete
很容易知道需要调用多少次析构函数。为单个对象分配的内存则不保存这一信息。你可以将这一差别想象成下边图中的样子,其中
n
是数组的大小:
当然这仅仅是一个示例,并没有强制指标要求编译器以这种形式实现,尽管许多编译器确实是这样的。
当你对一个指针使用
delete
时,如何让
delete
知道这一指针是否存在数组信息呢?这里只有一种方法,那就是亲自告诉它。如果你在
delete
与指针名之间添加一对中括号,则
delete
便认为这一指针指向一个数组。否则将以单一对象处理。
std::string *stringPtr1 = new std::string;
std::string *stringPtr2 = new std::string[100];
...
delete
stringPtr1; //
删除一个对象
delete []
stringPtr2; //
删除一个对象数组
如果你为
stringPtr1
使用“
[]
”时将会发生什么呢?我们说,这样做不会得到预期的效果。假设使用上面的内存分配形式,
delete
将会读入一些内存信息,并且将其理解为数组的长度,然后便开始调用这么多的析构函数,此时
delete
不仅忽视了它正在操作的内存上保存的并不是数组,同时它“辛辛苦苦”析构的东西很有可能都不是它所能操作的类型。
如果你不为
stringPtr2
使用“
[]
”将会发生什么呢?我们也可以说,这样做同样的不到预期的效果。你可以看到由于它没有调用足够的析构函数,于是将造成内存泄漏。同时,对于内建数据类型,诸如
int
等,尽管它们没有析构函数,但同样也会带来无法预期的结果(有时是有害的)。
这里的规则很简单:如果你在一个
new
语句中使用了
[]
,那么你必须在相关的
delete
语句中也使用
[]
。反之亦然。
有时候你会编写这样的类:它们包含用来动态分配内存的指针,并且提供多个构造函数。此时你需要时刻注意遵守上面的规则。在所有的构造函数中,当你编写初始化指针成员的语句时,你必须使用
new
的一致的格式。如果你不这样做,那么你怎么能知道析构函数中
delete
需要用什么样的格式呢?
如果你倾向于使用
typedef
,那么这一规则同样值得你注意,因为它意味着
typedef
的创建者必须清楚:当
typedef
的类型中使用了
new
来创建对象,那么相应的
delete
语句中必须要使用同样的格式。请看下边的示例:
typedef std::string AddressLines[4];
//
每个人的地址有
4
行,
//
每行都是一个字符串
由于
AddressLines
是一个数组,
如果这样使用了
new
:
std::string *pal = new AddressLines;
//
请注意“
new AddressLines
”
//
返回一个
string*
,
//
与“
new string[4]
”完全一样
那么
delete
就必须使用数组的格式:
delete pal; //
将出现无法预知的行为!
delete [] pal; //
工作正常
为了避免此类混淆,请谨慎使用
typedef
来定义数组。这十分简单,因为标准
C++
库(参见第
54
条)中包含了
string
和
vector
,使用这些模板可以摆脱动态分配数组的烦恼。比如说,在这里,
AddressLines
可以定义为一个字符串的向量,也就是
vector<string>
类型。
牢记在心
l
如果你在一个
new
语句中使用了
[]
,那么你必须要在相关的
delete
语句中使用
[]
。如果你在
new
语句中没有使用
[]
,那么在相关的
delete
语句中一定不要出现
[]
。