CPP智能指针浅探
本文最后更新于 2024年11月8日 下午
智能指针
智能指针是c++11引入的新特性,其本质是封装了一个原始c++指针的类模板,为了确保动态内存的安全性而产生的。实现原理是通过一个对象存储需要被自动释放的资源,然后依靠对象的析构函数来释放资源。其头文件是#include <memory>
unique_ptr
1、介绍
- unique_ptr 不共享它的指针。它无法复制到其他 unique_ptr,无法通过值传递到函数,也无法用于需要副本的任何标准模板库 (STL) 算法。只能移动unique_ptr。当需要智能指针用于纯 C++ 对象时,可使用 unique_ptr,而当构造 unique_ptr 时,可使用make_unique 函数。
- std::unique_ptr 实现了独享所有权的语义。转移一个 std::unique_ptr 将会把所有权也从源指针转移给目标指针(源指针被置空)。拷贝一个 std::unique_ptr 将不被允许,因为如果你拷贝一个 std::unique_ptr ,那么拷贝结束后,这两个 std::unique_ptr 都会指向相同的资源,它们都认为自己拥有这块资源(所以都会企图释放)。因此 std::unique_ptr 是一个仅能移动(move_only)的类型。
2、创建unique_ptr
- unique_ptr 不像 shared_ptr 一样拥有标准库函数 make_shared 来创建一个 shared_ptr 实例。要想创建一个 unique_ptr,我们需要将一个new 操作符返回的指针传递给 unique_ptr 的构造函数。
- std::make_unique是C++14才有的特性。
1 | |
3、无法进行复制构造和赋值操作
- unique_ptr没有copy构造函数,不支持拷贝和赋值操作。
1 | |
4、可以进行移动构造和移动赋值操作
- 可以移交所有权,使用std::move()函数。
1 | |
5、可以返回unique_ptr
1 | |
6、unique_ptr一些使用场景
- 在容器中保存指针
1 | |
- 管理动态数组
1 | |
- unique_ptr可以不占用对象,即为空。可以通过reset或者赋值nullptr释放管理对象
- 标准库早期版本中定了auto_ptr,它具有unique_ptr的部分特征,但不是全部。例如不能在容器中保存auto_ptr,不能从函数中返回auto_ptr等等,这也是unique_ptr主要的使用场景。
shared_ptr
1、介绍
shared_ptr是存储动态创建对象的指针,其主要功能是管理动态创建对象的销毁,帮助消除内存泄漏和悬空指针的问题。
其原理是记录对象被引用次数,当引用次数为0时,也就是最后一个指向该对象的共享指针析构的时候,共享指针的析构函数就把指向的内存区域释放掉。
共享指针内存:每个shared_ptr对象在内部指向两个内存位置。
- 指向对象的指针;
- 用于控制引用计数的指针。
2、shared_ptr的创建
构建空的智能指针:
1 | |
明确其指向。
1 | |
还可以使用相应的拷贝构造函数和移动构造函数。
1 | |
在初始化shared_ptr智能指针时,还可以自定义所指堆内存的释放规则,这样当堆内存的引用计数为0时,会优先调用自定义的释放规则。
对申请的动态数组,释放规则可以用c++11标准中提供的default_delete模板类。
1 | |
3、share_ptr常用函数
- get()函数,返回当前存储的指针
1 | |
- use_count()函数,当前引用计数
1 | |
- reset()函数,表示重置当前存储的指针
1 | |
weak_ptr
1、介绍
weak_ptr主要针对shared_ptr的空悬指针和循环引用问题而提出:
空悬指针问题:有两个指针p1和p2,指向堆上的同一个对象Object,p1和p2位于不同的线程中。假设线程A通过p1指针将对象销毁了(尽管把p1置为了NULL),那p2就成了空悬指针。weak_ptr不控制对象的生命期,但是它知道对象是否还活着。如果对象还活着,那么它可以提升为有效的shared_ptr(提升操作通过lock()函数获取所管理对象的强引用指针);如果对象已经死了,提升会失败,返回一个空的shared_ptr。
循环应用问题
1 | |
如上代码,将在程序退出前,ap的引用计数为2,bp的计数也为2,退出时,shared_ptr所作操作就是简单的将计数减1,如果为0则释放,显然,这个情况下,引用计数不为0,于是造成ap和bp所指向的内存得不到释放,导致内存泄露。
使用weak_ptr可以打破这样的循环引用。由于弱引用不更改引用计数,类似普通指针,只要把循环引用的一方使用弱引用,即可解除循环引用。以上述代码为例,只要把B类的代码修改为如下即可:
1 | |
最后值得一提的是,虽然通过弱引用指针可以有效的解除循环引用,但这种方式必须在能预见会出现循环引用的情况下才能使用,即这个仅仅是一种编译期的解决方案,如果程序在运行过程中出现了循环引用,还是会造成内存泄漏的。因此,不要认为只要使用了智能指针便能杜绝内存泄漏。
weak_ptr创建
创建一个空weak_ptr
1 | |
weak_ptr 指针更常用于指向某一 shared_ptr 指针拥有的堆内存,因为在构建 weak_ptr 指针对象时,可以利用已有的 shared_ptr 指针为其初始化。例如:
1 | |
weak_ptr模板类提供的成员方法
- operator=():重载 = 赋值运算符,是的 weak_ptr 指针可以直接被 weak_ptr 或者 shared_ptr 类型指针赋值。
- swap(x):其中 x 表示一个同类型的 weak_ptr 类型指针,该函数可以互换 2 个同类型 weak_ptr 指针的内容。
- reset():将当前 weak_ptr 指针置为空指针。
- use_count():查看指向和当前 weak_ptr 指针相同的 shared_ptr 指针的数量。
- expired():判断当前 weak_ptr 指针为否过期(指针为空,或者指向的堆内存已经被释放)。
- lock():如果当前 weak_ptr 已经过期,则该函数会返回一个空的 shared_ptr 指针;反之,该函数返回一个和当前 weak_ptr 指向相同的 shared_ptr 指针。