Fork me on GitHub

读书笔记 effective c++ Item 16 成对使用new和delete时要用相同的形式

1. 一个错误释放内存的例子

下面的场景会有什么错?

1 std::string *stringArray = new std::string[100];
2 
3 ...
4 
5 delete stringArray

 

一切看上去都是有序的。new匹配了一个delete。但有一些地方确实是错了。程序的行为是未定义的。至少来说,stringArray指向的100个string对象中的99个看上去都不能被正确释放,因为他们的析构函数可能永远不会被调用。

2. 使用new 和delete时究竟做了啥?

当你使用一个new表达式(通过使用new动态的创建一个对象)时,会发生两件事情。第一,内存被分配(通过一个叫做operator new的函数,看Item 49和Item 51)。第二,在分配的内存上调用了一个或多个构造函数。当你使用一个delete表达式时,另外两件事情会发生:在内存上调用了一个或者多个析构函数,然后内存被解除分配(通过调用叫做operator delete的函数,见 Item 51)。关于delete的一个重要的问题是:在即将被删除的内存中究竟有多少对象?这个问题的答案决定了有多少个析构函数必须被调用。

 

3. new和delete不配对使用为啥会出错?

实际上,下面这个问题更加简单:被删除的指针是指向一个单独的对象还是指向数组的所有对象?这是个关键的问题,因为单个对象的内存分配通常情况下同数组的内存分配是不一样的。特别的,一个数组的内存通常包含了数组的大小,因此delete很容易就会知道需要调用多少个析构函数。单个对象的内存却没有这样的信息。你可以将内存不同分配想象成下面这个样子,n是数组的大小:

 

当然这只是一个例子。编译器不需要这么实现,虽然很多编译器确实是这么实现的。

当你在一个指针上使用delete时,delete能够知道数组容量信息是否存在的唯一方法就是通过你来告诉它。如果当你使用delete时用了“[]”,delete认为指针指向一个数组。否则,它会认为它在指向一个单一的对象:

1 std::string *stringPtr1 = new std::string;
2 
3 std::string *stringPtr2 = new std::string[100];
4 
5 ...
6 
7 delete stringPtr1; // delete an object
8 
9 delete [] stringPtr2; // delete an array of objects

 

4. new和delete不配对使用会有什么后果?

如果你在stringPtr1上使用“[]”将会发生什么?结果是未定义的,但是结果不会太好。假设内存分布如上图所示,delete会读取一些内存并把它所读到的解释为一个数组容量,接下来就开始多次调用析构函数,却忽略的以下事实:它处理的内存不但不是一个数组,也可能并没有包含它正忙着释放的那种类型的对象。

如果你不在stringPtr2上使用“[]”会发生什么?结果也是未定义的,但是你可以看到这会导致过少的构造函数被调用。此外,对于像int的内建类型来说结果也是未定义的(有时甚至是有害的),虽然内建类型没有析构函数。

规则很简单:如果你在一个new表达式中使用”[]”,你必须在对应的delete表达式中使用”[]”,反之亦然

当你实现一个包含指向动态分配内存的指针的类,并且同时提供多个构造函数的时候,你需要将上面的重要规则记在心中,因为你必须当心在对构造函数中对指针成员进行初始化时,new必须使用相同的形式。如果你不这么做,你又怎么能知道在析构函数中将使用什么形式的delete呢?

5. 使用typedef时需要注意new和delete的配对使用

对于倾向于使用typedef的人来说这条规则同样值得注意,因为这意味着typedef的作者必须指出使用new来创建typedef类型的对象时,使用什么形式的delete对其进行销毁。看下面的例子:

1 typedef std::string AddressLines[4]; // a person’s address has 4 lines,
2 
3 // each of which is a string

 

因为AddressLines是一个数组,new应该这么使用:

1 std::string *pal = new AddressLines; // note that “new AddressLines”
2 
3 // returns a string*, just like
4 
5 // “new string[4]” would

使用delete的形式必须和new相匹配:

1 delete pal; // undefined!
2 
3 delete [] pal; // fine

 

为了避免这种混淆,不如放弃在数组类型上使用typedef。这很容易,因为标准c++库(见Item 54)中包含string,vector和模板,使得对动态分配数组的需求几乎将为0。这里我们举个例子,AddressLines可以被定义成由strings组成的vector,也就是类型 vector<string>。

posted @ 2017-02-21 08:05  HarlanC  阅读(1033)  评论(0编辑  收藏  举报