洛译小筑

别来无恙,我的老友…
随笔 - 45, 文章 - 0, 评论 - 172, 引用 - 0
数据加载中……

[ECPP读书笔记 条目16] 互相关联的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语句中也不应使用[]

有时候你会编写这样的类:它们包含用来动态分配内存的指针,并且提供多个构造函数。此时你需要时刻注意遵守上面的规则。在所有的构造函数中,你必须使用一致格式的new来初始化指针成员。如果你不这样做,你怎么能知道析构函数中delete需要用什么样的格式呢?

如果你倾向于使用typedef,那么这一规则同样值得你注意,因为它意味着:当你使用了new来创建typedef类型的对象时,至于应该使用delete语句的哪种形式,typedef的作者必须事先做出说明。请看下边的示例:

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)中包含了stringvector,使用这些模板可以摆脱动态分配数组的烦恼。比如说,在这里,AddressLines可以定义为一个字符串的向量,也就是vector<string>类型。

时刻牢记

如果你在一个new语句中使用了[],那么你必须要在相关的delete语句中使用[]。如果你在new语句中没有使用[],那么在相关的delete语句中一定不要出现[]

posted on 2007-05-14 22:15 ★ROY★ 阅读(1077) 评论(0)  编辑 收藏 引用 所属分类: Effective C++


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理