智能指针
智能指针的作用
C++程序设计中使用堆内存是非常频繁的操作,堆内存的申请和释放都由程序员自己管理。程序员自己管理堆内存可以提高了程序的效率,但是整体来说堆内存的管理是麻烦的,C++11中引入了智能指针的概念,方便管理堆内存。使用普通指针,容易造成堆内存泄露(忘记释放),二次释放,程序发生异常时内存泄露等问题等,使用智能指针能更好的管理堆内存。
理解智能指针需要从下面三个层次:
- 从较浅的层面看,智能指针是利用了一种叫做
RAII
(资源获取即初始化)的技术对普通的指针进行封装,这使得智能指针实质是一个对象,行为表现的却像一个指针。 - 智能指针的作用是防止忘记调用delete释放内存和程序异常的进入catch块忘记释放内存。另外指针的释放时机也是非常有考究的,多次释放同一个指针会造成程序崩溃,这些都可以通过智能指针来解决。
- 智能指针还有一个作用是把值语义转换成引用语义
智能指针和管理的对象分别在哪个区
智能指针本身在栈区,托管的资源在堆区,利用了栈对象超出生命周期后自动析构的特征,所以无需手动delete释放资源。
智能指针对象位于栈内存,管理一个堆内存的对象,当智能指针对象的生命周期结束后,会在析构函数中释放掉管理的堆内存资源,从而防止堆内存对象内存泄漏。
unique_ptr能否被另一个unique_ptr拷贝呢?
- 不能,因为它把它的拷贝构造函数
private
了。但是它提供了一个移动构造函数,所以可以通过std::move
将指针指向的对象交给另一个unique_ptr
,转交之后自己就失去了这个指针对象的所有权,除非被显示交回。
unique_ptr = unique_ptr和shared_ptr=shared_ptr这两个操作有什么后果呢?
对于unique_ptr` :
- 这个操作是不允许的,因为
unique_ptr
它的原理是将拷贝构造和拷贝赋值私有化,但是它提供了移动构造和移动赋值。所以如果你想要使用=赋值,必须先把右边的用std::move
包裹一下,这样右边的unique_ptr
就会失去所有权,左边的unique_ptr
就会得到对应对象的所有权
至于shared_ptr
:
- 对于左边的指针,它会将自己的引用计数减一,然后检测一下是不是减到了0,如果是,那么
delete
所管理的对象 - 然后将右边的引用计数和管理对象赋值给左边,此时两边指向同一个对象,共享同一个引用计数,然后引用计数++
shared_ptr的移动赋值时发生了什么事情
- 首先它会检查本指针和参数指针是不是同一个对象,如果是,直接返回
- 然后,先把本指针的引用变量–1,如果发现减到了0,就把参数指针和参数引用变量析构掉并置NULL
- 最后,本指针和参数指针指向同一个对象以及引用计数,然后引用计数自增1
unique_ptr和shared_ptr的区别
unique_ptr
代表的是专属所有权,不支持复制和赋值。但是可以移动shared_ptr
代表的是共享所有权,shared_ptr
是支持复制的和赋值以及移动的unique_ptr
在默认情况下和裸指针的大小是一样的。
所以 内存上没有任何的额外消耗,性能是最优的,我们大多数场景下用到的应该都是unique_ptr
。shared_ptr
的内存占用是裸指针的两倍。因为除了要管理一个裸指针外,还要维护一个引用计数。因此相比于unique_ptr
,shared_ptr
的内存占用更高。在使用shared_ptr
之前应该考虑,是否真的需要使用shared_ptr
, 而非unique_ptr
。shared_ptr你是怎么实现的
shared_ptr
内部是利用引用计数来实现内存的自动管理,每当复制一个shared_ptr
,引用计数会 + 1。当一个shared_ptr
离开作用域时,引用计数会 - 1。当引用计数为 0 的时候,则delete
内存。shared_ptr是不是线程安全
不是
引用计数的增减是原子操作没问题,但是
shared_pytr
的读写本身不只包括引用计数操作,还包括资源所有权的操作,这两个操作合起来不是原子的如果要求线程安全必须加锁
资源所有权操作不是线程安全的:包括指针的赋值和删除操作,如果在多线程环境中使用,需要加锁保护。
加锁保护资源所有权操作:在多线程环境中对
shared_ptr
的赋值或修改操作应使用互斥锁(如std::mutex
)来保护,以确保线程安全。
weak_ptr如何检测指针是否被销毁
expired()
:- 判断强引用计数是否为0
- 如果返回true,那么被观测的对象(也就是
shared_ptr
管理的资源)已经不存在了
如何将weak_ptr转换为shared_ptr
用
lock()
:- 如果
expired()
为true,返回一个空shared_ptr
,否则返回非空shared_ptr
智能指针的设计与实现
智能指针类将一个计数器与类指向的对象相关联,引用计数跟踪该类有多少个对象共享同一指针。每次创建类的新对象时,初始化指针并将引用计数置为1;当对象作为另一对象的副本而创建时,拷贝构造函数拷贝指针并增加与之相应的引用计数;对一个对象进行赋值时,赋值操作符减少左操作数所指对象的引用计数(如果引用计数为减至0,则删除对象),并增加右操作数所指对象的引用计数;调用析构函数时,构造函数减少引用计数(如果引用计数减至0,则删除基础对象)。智能指针就是模拟指针动作的类。所有的智能指针都会重载 -> 和 * 操作符。智能指针还有许多其他功能,比较有用的是自动销毁。这主要是利用栈对象的有限作用域以及临时对象(有限作用域实现)析构函数释放内存。
1 |
|
shared_ptr
shared_ptr
使用了引用计数,每一个shared_ptr
的拷贝都指向相同的内存,每次拷贝都会触发引用计数+1,每次生命周期结束析构的时候引用计数-1,在最后一个shared_ptr
析构的时候,内存才会释放。
关于shared_ptr
有几点需要注意:
• 不要用一个裸指针初始化多个shared_ptr
,会出现double_free
导致程序崩溃
• 通过shared_from_this()
返回this
指针,不要把this
指针作为shared_ptr
返回出来,因为this
指针本质就是裸指针,通过this
返回可能会导致重复析构,不能把this
指针交给智能指针管理。
1 | class A { |
- 尽量使用
make_shared
,少用new
。 - 不要
delete get()
返回来的裸指针。 - 不是
new
出来的空间要自定义删除器。 - 要避免循环引用,循环引用导致内存永远不会被释放,造成内存泄漏。
1 |
|
1 | construct |
weak_ptr
weak_ptr
是用来监视shared_ptr
的生命周期,它不管理shared_ptr
内部的指针,它的拷贝的析构都不会影响引用计数,纯粹是作为一个旁观者监视shared_ptr
中管理的资源是否存在,可以用来返回this
指针和解决循环引用问题。
- 作用1:返回
this
指针,上面介绍的shared_from_this()
其实就是通过weak_ptr
返回的this
指针。 - 作用2:解决循环引用问题。
空悬指针问题:有两个指针p1
和p2
,指向堆上的同一个对象Object
,p1
和p2
位于不同的线程中。假设线程A通过p1指针将对象销毁了(尽管把p1
置为了NULL),那p2
就成了空悬指针。
weak_ptr
不控制对象的生命期,但是它知道对象是否还活着。如果对象还活着,那么它可以提升为有效的shared_ptr
(提升操作通过lock()
函数获取所管理对象的强引用指针);如果对象已经死了,提升会失败,返回一个空的shared_ptr
。
1)operator=():重载 = 赋值运算符,是的 weak_ptr 指针可以直接被 weak_ptr 或者 shared_ptr 类型指针赋值。
2)swap(x):其中 x 表示一个同类型的 weak_ptr 类型指针,该函数可以互换 2 个同类型 weak_ptr 指针的内容。
3)reset():将当前 weak_ptr 指针置为空指针。
4)use_count():查看指向和当前 weak_ptr 指针相同的 shared_ptr 指针的数量。
5)expired():判断当前 weak_ptr 指针为否过期(指针为空,或者指向的堆内存已经被释放)。
6)lock():如果当前 weak_ptr 已经过期,则该函数会返回一个空的 shared_ptr 指针;反之,该函数返回一个和当前 weak_ptr 指向相同的 shared_ptr 指针。
unique_ptr
std::unique_ptr
是一个独占型的智能指针,它不允许其它智能指针共享其内部指针,也不允许unique_ptr
的拷贝和赋值。使用方法和shared_ptr
类似,区别是不可以拷贝