参考文章:auto_ptr, unique_ptr, shared_ptr and weak_ptr
参考文章:auto_ptr, unique_ptr, shared_ptr and weak_ptr智能指针讲解
C++ 库提供以下类型的智能指针实现:
- auto_ptr
- unique_ptr
- shared_ptr
- weak_ptr
头文件和命名空间:
1 |
|
auto_ptr
模板 auto_ptr 是 C++98 提供的解决方案,从 C++11 开始,此类模板被弃用。 unique_ptr 是具有相似功能但具有改进的安全性的新工具。
auto_ptr 是一个智能指针,用于管理通过 new 表达式获取的对象,并在 auto_ptr 本身被销毁时删除该对象。
使用 auto_ptr 类描述的对象,它存储指向单个已分配对象的指针,该对象确保当它超出范围时,它指向的对象必须自动销毁。它基于 独占所有权模型 ,即同一类型的两个指针不能同时指向同一资源。如下面所示,复制或指定指针会改变所有权,即源指针必须赋予目标指针所有权。
1 |
|
输出:
[INFO]: ----------auto_ptr----------
[INFO]: A
[INFO]: pa1 address: 0x21f7010
[INFO]: pa1 address: 0
[INFO]: pa2 address: 0x21f7010
[INFO]: ~A
auto_ptr 的拷贝构造函数和赋值运算符实际上并没有复制存储的指针,而是将它们传输出去,是第一个 auto_ptr 对象变为空。这是实现严格所有权的一种方法,因此只有一个 auto_ptr 对象可以在任何给定时间拥有该指针,即在需要复制语义的情况下不应使用 auto_ptr。
auto_ptr 被弃用原因:
独占所有权的方式获取指针的所有权。赋值转移所有权并将 之前的 auto_ptr 对象重置为空指针,可导致出现悬空指针,存在潜在的内存崩溃风险。因此,由于上述无法复制,它们不能在STL容器内使用。
unique_ptr
unique_ptr 是在 C++11 中开发的,可替代 auto_ptr 。
unique_ptr 是一个具有类似功能的新的智能指针模板,具有更高的安全性(无伪造复制分配),添加删除器功能和数组支持。它是一个原始指针的容器,它明确的防止复制其包含的指针,只允许底层指针的一个所有者,因此,当使用 unique_ptr 时,在任何一个资源上最多只能有一个 unique_ptr 。
由于任何资源只能有一个 unique_ptr,因此任何尝试复制 unique_ptr 都会导致编译时错误:
1 | std::unique_ptr<A> pa1(new A); |
unique_ptr 可以使用新的语义,即使用 std::move() 函数将包含的指针的所有权转移到另一个 unique_ptr。
1 | std::unique_ptr<A> pa1(new A); |
输出:
[INFO]: A
[INFO]: pa1 address: 0x23c2010
[INFO]: pa1 address: 0
[INFO]: pa2 address: 0x23c2010
[INFO]: ~A
下面的代码返回一个资源,如果我们没有显式捕获返回值,那么资源将被清除。如果我们这样做,那么我们拥有该资源的独家所有权。通过这种方式,我们可以将 unique_ptr 视为更安全,更好的 auto_ptr 替代品。
1 | std::unique_ptr<A> make_A() |
std::unique_ptr 设置删除器的方法:
1 | //默认删除器 |
释放 unique_ptr 所有权:
1 | std::unique_ptr<A> pa1(new A); |
输出:
[INFO]: A
[INFO]: pa1 address: 0
[INFO]: ~A
最好使用 unique_ptr ,当我们想要一个指向一个对象的指针,当该单个指针被销毁时将被自动回收:
1 | PRINT_INFO("---------unique_ptr---------"); |
输出:
[INFO]: ---------unique_ptr---------
[INFO]: A
[INFO]: pa1 address: 0x23c2010
[INFO]: pa1 address: 0x23c2010
[INFO]: pa1 address: 0
[INFO]: pa2 address: 0x23c2010
[INFO]: A
[INFO]: pa3 address: 0x23c2030
[INFO]: pa3 address: 0
[INFO]: ~A
[INFO]: ~A
何时使用 unique_ptr ?
当您想拥有资源的唯一所有权(Exclusive)时,请使用 unique_ptr, 只有一个 unique_ptr 可以指向一个资源, 因为单个资源可以有一个 unique_ptr,所以不可能将一个 unique_ptr 复制到另一个。
shared_ptr
shared_ptr 是原始指针的容器。 它是一个引用计数模型,即它与shared_ptr 的所有副本合作维护其包含的指针的引用计数。 因此,每当一个新的指针指向资源时,计数器就会递增,当调用对象的析构函数时递减计数器。
引用计数:计算机编程语言中的一种内存管理技术,是指将资源(可以是对象、内存或磁盘空间等等)的被引用次数保存起来,当被引用次数变为零时就将其释放的过程。
引用计数大于0,所包含的原始指针引用的对象将不会被销毁,直到所有的 shared_ptr 副本都被删除。因此,当我们要为一个原始指针分配给多个所有者时,我们应该使用 shared_ptr。
1 | PRINT_INFO("---------shared_ptr---------"); |
输出:
[INFO]: ---------shared_ptr---------
[INFO]: A
[INFO]: pa1 address: 0x254f010
[INFO]: pa1 address: 0x254f010
[INFO]: pa2 address: 0x254f010
[INFO]: pa3 address: 0x254f010
[INFO]: use count: 3
[INFO]: use count: 3
[INFO]: use count: 3
[INFO]: pa1 address: 0
[INFO]: pa2 address: 0x254f010
[INFO]: pa3 address: 0x254f010
[INFO]: use count: 0
[INFO]: use count: 2
[INFO]: use count: 2
[INFO]: ~A
std::shared_ptr 设置删除器的方法:
1 | std::shared_ptr<A> pa(new A[3], [](A* pa){delete []pa;}); |
只有用一个 shared_ptr 为另一个 shared_ptr 赋值时,才将这连个共享指针关联起来,直接使用地址值会导致各个 shared_ptr 独立。
1 | A *pa = new A; |
输出:
[INFO]: pa1 use count: 2
[INFO]: pa2 use count: 2
[INFO]: pa3 use count: 1
何时使用 shared_ptr ?
如果要共享资源的所有权,请使用 shared_ptr。许多 shared_ptr 可以指向单个资源。 shared_ptr 维护资源的引用计数。当所有指向资源的shared_ptr超出范围时,资源将被销毁。
share_ptr 完美支持标准容器,并且不需要担心资源泄漏。
weak_ptr
std::weak_ptr 是一种智能指针,它对被 std::shared_ptr 管理的对象存在非拥有性(“弱”)引用(不参与引用计数),weak_ptr 的存在或破坏对 shared_ptr 或其他副本没有影响,在访问所引用的对象前必须先转换为 std::shared_ptr 。
在某些情况下需要中断 shared_ptr 实例之间的循环引用,防止内存泄露。
循环依赖( shared_ptr 的问题):让我们考虑一个有两个类 A 和 B 的场景,A 中包含 B 的指针变量,B 中包含 A 的指针变量。有如下所示关系时:
use_count 将永远不会达到零并且它们永远不会被删除,将造成内存泄露。
1 | PRINT_INFO("----------weak_ptr----------"); |
输出(内存泄露):
[INFO]: ----------weak_ptr----------
[INFO]: struct A()
[INFO]: struct B()
[INFO]: pa use_count: 1
[INFO]: pb use_count: 1
[INFO]: pa use_count: 3
[INFO]: pb use_count: 3
这就是我们使用弱指针(weak_ptr)的原因,因为它们不是引用计数的。因此,声明 weak_ptr 的类不具有强大的保持力,即所有权不是共享的,但是它们可以访问这些对象。
1 | PRINT_INFO("----------weak_ptr----------"); |
输出:
[INFO]: ----------weak_ptr----------
[INFO]: struct A()
[INFO]: struct B()
[INFO]: pa use_count: 1
[INFO]: pb use_count: 1
[INFO]: pa use_count: 1
[INFO]: pb use_count: 1
[INFO]: struct ~B()
[INFO]: struct ~A()
1 | std::shared_ptr<A> pa(new A); |
输出:
[INFO]: A
[INFO]: pwa use_count: 1
[INFO]: Show
[INFO]: ~A
std::weak_ptr 用来表达临时所有权的概念:当某个对象只有存在时才需要被访问,而且随时可能被他人删除时,可以使用 std::weak_ptr 来跟踪该对象。需要获得临时所有权时,则将其转换为 std::shared_ptr,此时如果原来的 std::shared_ptr 被销毁,则该对象的生命期将被延长至这个临时的 std::shared_ptr 同样被销毁为止。
std::weak_ptr 的另一用法是打断 std::shared_ptr 所管理的对象组成的环状引用。若这种环被孤立(例如无指向环中的外部共享指针),则 shared_ptr 引用计数无法抵达零,而内存被泄露。能令环中的指针之一为弱指针以避免此情况。
1 | //reset |
输出:
[INFO]: ---------------auto_ptr reset---------------
[INFO]: A
[INFO]: ~A
[INFO]: reset() pa1 end
[INFO]: A
[INFO]: reset(new A) pa1 end
[INFO]: ~A
[INFO]: --------------unique_ptr reset--------------
[INFO]: A
[INFO]: ~A
[INFO]: reset() pa1 end
[INFO]: A
[INFO]: reset(new A) pa1 end
[INFO]: ~A
[INFO]: --------------shared_ptr reset--------------
[INFO]: A
[INFO]: use count:2
[INFO]: A
[INFO]: use count:1
[INFO]: ~A
[INFO]: ~A
[INFO]: ---------------weak_ptr reset---------------
[INFO]: A
[INFO]: use count: 1
[INFO]: use count: 1
[INFO]: ~A