程序使用三种不同的内存
- 静态内存:static成员以及任何定义在函数之外的变量
- 栈内存:一般局部变量
- 堆内存(自由空间):动态分配的对象
静态内存和栈内存中的变量由编译器产生和销毁,动态分配的对象在我们不再使用它时要由程序员显式地销毁
1|0一、介绍
动态分配内存
new()
:为对象分配空间,并返回指向该对象的指针delete
:销毁对象,并释放与之相关的内存
使用智能指针:定义在头文件memory中
shared_ptr
:允许多个指针指向同一个对象unique_ptr
:“独占”所使用的对象weak_ptr
:伴随类,弱引用,指向shared_ptr所管理的对象
和容器一样,只能指针也是一种模板,需要给它传入一个参数来指定类型
2|0二、shared_ptr类
声明shared_ptr:
使用方式与普通指针一致,解引用返回它所指向的对象,在条件表达式中检查是否为空
2|1make_shared函数
make_shared<typename>(arguments)
在动态内存中分配并初始化一个对象
返回指向此对象的shared_ptr指针
没有传入参数时,进行值初始化
2|2shared_ptr的拷贝和引用
每个share_ptr都有一个关联的计数器
- 当拷贝shared_ptr时,计数器会递增
- 当shared_ptr被赋予新值或者shared_ptr被销毁(如一个局部的shared_ptr离开其作用域),计数器会递减
- 当一个shared_ptr的计数器==0时,内存会被释放
2|3shared_ptr自动销毁所管理的对象…
和其他类一样,shared_ptr类型也有析构函数
shared_ptr的析构函数会
- 递减指针所指向的对象的引用计数
- 当对象的引用计数为0时,销毁对象并释放内存
2|4…shared_ptr还会自动释放相关联对象的内存
举例:
如果有其他引用计数也指向该对象,则对象内存不会被释放掉
return shared_ptr时,如果不是返回引用类型,则会进行拷贝,shared_ptr的计数器+1后-1,最终shared的计数器不变
由于在最后一个shared _ptr销毁前内存都不会释放,保证shared_ptr在无用之后不再保留就非常重要了。如果你忘记了销毁程序不再需要的shared_ptr,程序仍会正确执行,但会浪费内存。
share_ptr 在无用之后仍然保留的一种可能情况是,你将shared _ptr存放在一个容器中,随后重排了容器,从而不再需要某些元素。在这种情况下,你应该确保用erase删除那些不再需要的shared_ptr元素。
如果你将shared ptr存放于一个容器中,而后不再需要全部元素,而只使用其中一部分,要记得用erase删除不再需要的那些元素。
2|5使用动态生存期的资源的类
程序使用动态内存的三种原因
- 程序不知道自己需要使用多少对象
- 不知道所需对象的准确类型
- 需要在多个对象间共享数据
容器类常出于第一种原因使用动态内存,在15章会看见出于第二种原因的例子,本节讨论第三种原因
先考虑这么一种情况:
我们要定义一个Blob类,当该类型的对象拷贝时,对象共享底层数据。
如b2 = b1时,b2,b1共享底层数据,对b2的操作也会印象到b1,且销毁b2时,b1的仍指向原数据
2|6应用举例:Blob类
定义Blob类
最终,我们希望将Blob定义为一个模板类,但现在我们先将其定义为StrBlob,即底层数据是vector<string>的Blob
StrBlob的构造函数
元素访问成员函数
在访问时必须保证容器非空,定义check函数进行检查
元素访问成员函数:
StrBlob的拷贝、赋值和销毁
StrBlob使用默认的拷贝、赋值和析构函数对此类型的对象进行操作
当我们对StrBlob对象进行拷贝、赋值和销毁时,它的shared_ptr成员也会默认地进行拷贝、赋值和销毁
测试程序:
输出结果:
如果
look_data
用值返回,而不是引用返回,那么会存在拷贝【见6.2.2节笔记】,所有计数器的值会+1
3|0三、直接管理内存
3|1使用new分配内存
new
分配动态内存delete
销毁动态内存
new和delete与智能指针不同,类对象的拷贝、赋值和销毁操作都不会默认地对动态分配的对象进行管理,无论是对象的创建还是销毁,都需要程序员显式地操作,在大型的应用场景中会十分复杂。
在熟悉C++拷贝控制之前,尽量只使用智能指针,而不是本节的方法管理动态内存
使用new动态分配和初始化对象
new type_name
:返回一个指向该对象的指针
对象是默认初始化这意味着:
指向的是:内置类型和组合类型对象。对象的值是未定义的
指向的是:类类型对象。调用默认构造函数
可以直接初始化动态分配的对象
- 直接调用构造函数
- 列表初始化
也可以值初始化
所以,初始化动态分配的对象是一个好习惯
动态分配const对象
用new
可以分配const对象
和其他const对象一样,动态分配的const对象必须被初始化
内存耗尽
如果new分配动态内存失败,返回一个空指针,并报出std::bad_alloc异常
我们第二种形式的new为定位new (placement new),其原因我们将在19.1.2节(第729页)中解释。
定位new表达式允许我们向new传递额外的参数。
在此例中,我们传递给它一个由标准库定义的名为nothrow的对象。如果将nothrow传递给new,我们的意图是告诉它不能抛出异常。如果这种形式的 new不能分配所需内存,它会返回一个空指针。bad_alloc和nothrow都定义在头文件new中。
3|2使用delete释放内存
基本介绍
delete()
:接受一个指针,指向我们想要销毁的对象
执行两个操作
- 销毁对象
- 释放对应的内存
注意点:
- 保证只传给delete动态分配的指针,将一般指针传给delete,其行为是未定义的
- 同一块内存不能释放两次
- 不要忘记delete内存
- 不要使用已经delete的对象
保证以上两点是程序员的责任,编译器并不会检查以上错误
举例
在被显式地delete前,用new动态分配的内存一直存在
use_factory返回时,局部变量p被销毁。但此变量是一个内置指针,而不是一个智能指针,所以p所指向的内存并没有被销毁。
这样就产生了一块无名的内存块,存在又无法删除。
这也体现了智能指针与普通指针的区别:智能指针在离开自己的作用域,自己的变量名失效时,销毁指向的对象并释放关联内存;而new产生的指针不会。
修改use_factory:
坚持使用智能指针,可以避免上述的绝大部分问题
4|0四、shared_ptr和new结合使用
4|1new直接初始化share_ptr
可以用new返回的指针初始化share_ptr
该构造函数是explicit的
所以,不存在new产生的指针向shared_ptr的隐式类型转换,必须采用直接初始化,而不是拷贝初始化或者赋值
同理,返回shared_ptr的函数不能返回new产生的指针
如对隐式类型转换有疑问查看 7-5笔记第三点”隐式类类型转换”
4|2初始化时传入可调用对象代替delete
默认情况下,一个用来初始化智能指针的普通指针必须指向动态内存,因为智能指针默认使用delete释放它所关联的对象。我们可以将智能指针绑定到一个指向其他类型的资源的指针上,但是为了这样做,必须提供自己的操作来替代 delete。我们将在12.1.4节介绍如何定义自己的释放操作。
5|0五、unique_ptr
和shared_ptr不同,某个时刻只能有一个unique_ptr指向一个给定对象
5|1基本操作
必须采用直接初始化
unique_ptr不支持拷贝与赋值
unique_ptr支持的操作
可以使用release和reset将指针的所有权从一个(非const)unique_ptr转移到另一个unique_ptr
5|2传递和返回unique_ptr
不能拷贝unique_ptr 的规则有一个例外:我们可以拷贝或赋值一个将要被销毁的unique_ptr。最常见的例子是从函数返回一个unique_ptr:
还可以返回一个局部变量的拷贝
对于两段代码,编译器都知道要返回的对象将要被销毁。在此情况下,编译器执行一种特殊的“拷贝”,我们将在13.6.2节(移动构造函数和移动运算符)中介绍它。
5|3向unique_ptr传递删除器
作为一个更具体的例子,我们将写一个连接程序,用unique_ptr来代替shared_ptr,如下所示:
注意decltype(end_connection)
返回一个函数类型,而函数类型不能作为参数,函数指针可以
所以要加上*
表示函数指针
p(&c, end_connection)
中,类似于数组名表示指针一样,函数名实际上就表示函数指针
所以也可写作p(&c, &end_connection)
,但没必要。【前一个&表示引用传递,后一个&表示取址得到指针】
__EOF__
本文链接:https://www.cnblogs.com/timothy020/p/15948748.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角【推荐】一下。您的鼓励是博主的最大动力!