C++智能指针
智能指针
智能指针的设计目的 是因为 当 new 一个指针时 如果忘记释放就会带来内存泄漏的问题。因此C++提供了智能指针的方法来“智能地”管理指针所指的内存,自动给你释放。通过new 申请返回的是指向 内存的 指针,智能指针是通过类 来对 这个指针进行管理。当超出了类的作用域,就会自动触发类的析构函数完成该指针指向的内存的释放功能。
综上所述,智能指针是通过类 来 对指针进行管理,当超过类的作用域 类的析构函数自动完成指针所指向的内存的释放功能。而为了使智能指针的表达具有 “指针变量” 类似的形式,需要对 指针变量常用的操作符 例如 “-> * = ” 等重定义。
- auto_ptr (c++11弃用)
- share_ptr (c++11)
- weak_ptr (c++11)
- unique_ptr (c++11)
auto_ptr
auto_ptr 就是对开头的思想进行了实现,是c98里的东西,由于有很多缺陷,c11中已经被弃用。
- 所有权问题。当把 一个auto_ptr 对象 A 通过拷贝构造给了 对象 B 此时A就是个空类,你再试图访问A所指向的空间就会越界。
- 不支持组管理 就是 通过
new int[ 500]
这种分配的数组 ,它无法管理,因为这时候释放应该使用delete [] A
而 auto_ptr中只支持 delete A直接释放内存。 - 不支持容器
1 | class Test |
unique_ptr
unique_ptr
也是对auto_ptr
的替换。unique_ptr
遵循着独占语义。在任何时间点,资源只能唯一地被一个unique_ptr
占有。当unique_ptr
离开作用域,所包含的资源被释放。如果资源被其它资源重写了,之前拥有的资源将被释放。所以它保证了他所关联的资源总是能被释放。但是如果程序试图将一个 unique_ptr 赋值给另一个时,如果源 unique_ptr 是个临时右值,编译器允许这么做;如果源 unique_ptr 将存在一段时间,编译器将禁止这么做,如:
1 | unique_ptr<string> pu1(new string ("hello world")); |
特性
unique_ptr
的创建方法和shared_ptr
一样,除非创建一个指向数组类型的unique_ptr
。unique_ptr
提供了创建数组对象的特殊方法,当指针离开作用域时,调用delete[]
代替delete
。当创建unique_ptr
时,这一组对象被视作模板参数的部分。这样,程序员就不需要再提供一个指定的析构方法unique_ptr
拷贝赋值和拷贝构造都不可以(编译报错),只支持移动语义(move semantics
).
接口
unique_ptr提供的接口和传统指针差不多,但是不支持指针运算。
unique_ptr
提供一个release()
的方法,释放所有权。release
和reset
的区别在于,release
仅仅释放所有权但不释放资源,reset
也释放资源。
share_ptr
对于上面 auto_ptr 最基本的 当拷贝构造后 指针的所有权被剥离 只剩野指针的问题,share_ptr 通过共享所有权的概念解决。share_ptr通过计数的机制实现 共享。
- 多个智能指针可以共同拥有/指向同一个 申请的内存/对象,当最后个智能指针离开作用域的时候,内存才会自动释放
- 包含两种计数 : 强引用计数 弱引用计数
创建
1 | void main( ) |
析构
shared_ptr
默认调用delete
释放关联的资源。如果用户采用一个不一样的析构策略时,他可以自由指定构造这个shared_ptr
的策略。应该调用delete[]
来销毁这个数组。用户可以通过调用一个函数,例如一个lamda
表达式,来指定一个通用的释放步骤
1 | Class Test |
接口
除了 指针常用的 操作 符*
,->
之外 它还提供了一些有用的接口
get()
: 获取shared_ptr
绑定的资源- swap():交换两个 share_ptr 对象 交换所有权
reset()
: 释放关联内存块的所有权,如果是最后一个指向该资源的shared_ptr
,就释放这块内存unique
: 判断是否是唯一指向当前内存的shared_ptr
operator bool
: 判断当前的shared_ptr
是否指向一个内存块,可以用 if 表达式判断
问题
- 对同一个裸指针使用两个 shared_ptr初始化管理。应该尽量避免这种情况。当其中一个智能指针离开作用域后释放该指针 另一个再用就出问题。尽量不要从一个裸指针
(naked pointer)
创建shared_ptr
. - 循环引用问题。如下面的例子,当离开main函数时,
sptrB
和sptrA
离开了作用域 (他们俩是在main函数中申请的一个类)就会导致计数值减一,但是不会释放内存,因为此时 new 的 结构 A 和 B 中包含的智能指针还在相互引用呢
1 | // 问题1 |
简单实现
1 | template<typename T> // 注意模板类的使用方法 |
weak_ptr
根据上面的分析,导致相互引用内存无法释放的主要原因是 两个类内部的share_ptr
之间的相互引用。导致管理该类的智能指针无法释放内存。因此提出 通过 weak_ptr
来 替代 类内部的 shared_ptr
。weak_ptr
只能由 shared_ptr
拷贝初始化,并且只增加 shared_ptr
的弱引用计数值,而在释放内存时只考虑强引用计数值,不管弱引用计数值为多少都不影响内存的释放。
基于以上思路,完善一下 weak_ptr 的定义和性质。
weak_ptr
只能由share_ptr
指针创建初始化,只增加 弱引用计数。将一个weak_ptr
赋给另一个weak_ptr
会增加弱引用计数(weak reference count
)weak_ptr
没有*
->
操作,它并不包含资源所以也不允许程序员操作资源。但是 在需要访问资源的时候,可以先将它转换为shared_ptr 再访问 如下:- 调用
expired()
方法 判断weak_ptr
是否指向有效资源 - 从
weak_ptr
调用lock()
可以得到shared_ptr
或者直接将weak_ptr
转型为shared_ptr
- 调用
1 | class B; |