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

简介: 1. 一个错误释放内存的例子 下面的场景会有什么错? 1 std::string *stringArray = new std::string[100]; 2 3 ... 4 5 delete stringArray   一切看上去都是有序的。

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>。


作者: HarlanC

博客地址: http://www.cnblogs.com/harlanc/
个人博客: http://www.harlancn.me/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出, 原文链接

如果觉的博主写的可以,收到您的赞会是很大的动力,如果您觉的不好,您可以投反对票,但麻烦您留言写下问题在哪里,这样才能共同进步。谢谢!

目录
相关文章
|
7月前
|
安全 C语言 C++
比较C++的内存分配与管理方式new/delete与C语言中的malloc/realloc/calloc/free。
在实用性方面,C++的内存管理方式提供了面向对象的特性,它是处理构造和析构、需要类型安全和异常处理的首选方案。而C语言的内存管理函数适用于简单的内存分配,例如分配原始内存块或复杂性较低的数据结构,没有构造和析构的要求。当从C迁移到C++,或在C++中使用C代码时,了解两种内存管理方式的差异非常重要。
251 26
【C++】深入解析C/C++内存管理:new与delete的使用及原理(二)
【C++】深入解析C/C++内存管理:new与delete的使用及原理
245 4
|
编译器 C++ 开发者
【C++】深入解析C/C++内存管理:new与delete的使用及原理(三)
【C++】深入解析C/C++内存管理:new与delete的使用及原理
293 3
|
存储 C语言 C++
【C++】深入解析C/C++内存管理:new与delete的使用及原理(一)
【C++】深入解析C/C++内存管理:new与delete的使用及原理
312 3
|
程序员 C语言 C++
C++入门5——C/C++动态内存管理(new与delete)
C++入门5——C/C++动态内存管理(new与delete)
293 1
C++(十九)new/delete 重载
本文介绍了C++中`operator new/delete`重载的使用方法,并通过示例代码展示了如何自定义内存分配与释放的行为。重载`new`和`delete`可以实现内存的精细控制,而`new[]`和`delete[]`则用于处理数组的内存管理。不当使用可能导致内存泄漏或错误释放。
|
NoSQL 编译器 Redis
c++开发redis module问题之如果Redis加载了多个C++编写的模块,并且它们都重载了operator new,会有什么影响
c++开发redis module问题之如果Redis加载了多个C++编写的模块,并且它们都重载了operator new,会有什么影响
|
存储 C语言 C++
【C/C++】动态内存管理( C++:new,delete)
C++的`new`和`delete`用于动态内存管理,分配和释放内存。`new`分配内存并调用构造函数,`delete`释放内存并调用析构函数。`new[]`和`delete[]`分别用于数组分配和释放。不正确匹配可能导致内存泄漏。内置类型分配时不初始化,自定义类型则调用构造/析构。`operator new`和`operator delete`是系统底层的内存管理函数,封装了`malloc`和`free`。定位`new`允许在已分配内存上构造对象,常用于内存池。智能指针等现代C++特性能进一步帮助管理内存。
|
存储 程序员 编译器
c++学习笔记08 内存分区、new和delete的用法
C++内存管理的学习笔记08,介绍了内存分区的概念,包括代码区、全局区、堆区和栈区,以及如何在堆区使用`new`和`delete`进行内存分配和释放。
201 0
|
NoSQL Redis C++
c++开发redis module问题之避免多个C++模块之间因重载operator new而产生的冲突,如何解决
c++开发redis module问题之避免多个C++模块之间因重载operator new而产生的冲突,如何解决