C++标准库——智能指针

@

auto_ptr

auto_ptr是C++11前标准库提供的一种智能指针,它具有unique_ptr的部分特性,但不是全部。特别是,我们不能在容器中保存auto_ptr,也不能从函数中返回auto_ptr。

其功能如下:

  • 支持拷贝构造
  • 支持赋值拷贝
  • 支持operator->/operator*解引用
  • 支持指针变量重置
  • 保证指针持有者唯一

它最大的限制是,为了保证所有权唯一,在拷贝复制后,会将原auto_ptr中管理的指针置空,此时再次操作原auto_ptr会导致空指针。这违反了赋值操作通常不改变源对象的常规预期。
例子:

        int *pi = new int(3);
        std::auto_ptr<int> ap(pi);
        std::auto_ptr<int> bp = ap;
        std::cout << *bp << std::endl;
        std::cout << *ap << std::endl;	// 空指针
        std::cout << *pi << std::endl;

运行结果:

3
Segmentation fault (core dumped)

因此,在C++11中被弃用,并最终在c++17中移除出去。

unique_ptr

unique_ptr提供了一种严格的所有权语义:

  • 一个unique_ptr拥有一个对象,它保存一个指针指向该对象。即,unique_ptr有责任用所保存的指针销毁所指向的对象
  • unique_ptr不能拷贝(没有拷贝构造函数和拷贝赋值操作),但可以移动
  • unique_ptr保存一个指针,当他自身被销毁时,使用关联的释放器(如果有的话)释放所指向的对象(默认版本使用delete来释放)

unique_ptr的用途包括:

  • 为动态分配的内存提供异常安全
  • 将动态分配内存的所有权传递给函数
  • 从函数返回动态分配的内存
  • 在容器中保存指针

我们可以将unique_ptr理解为一个简单指针(“包含指针”)或(如果有释放器的话)一对指针:
在这里插入图片描述

C++11标准库没有提供类似make_shared()的创建unique_ptr和函数make_unique,但是在c++14后便准库提供了,基本实现:

template< class T, class... Args >
unique_ptr<T> make_unique( Args&&... args )
{
	unique_ptr<T>(new T(std::forward<Args>(args)...))
}

shared_ptr

shared_ptr表示共享所有权。shared_ptr是一种计数指针,当计数变成0时释放所指向的对象。可以将shared_ptr理解为包含两个指针的结构:一个指针指向对象,一个指针指向计数器:
在这里插入图片描述
释放器deleter用来在计数器变成0时释放共享对象。默认释放器通常是delete(调用对象的析构函数并释放自由存储空间)。

shared_ptr是如何实现共享式指针?

在C++中,std::shared_ptr 是一种智能指针,用于管理动态分配的对象,并支持多个指针实例共享同一个对象的所有权。shared_ptr 通过引用计数来实现这一点。
主要实现机制:

  1. 引用计数

    • 每个 shared_ptr 对象持有一个指向动态分配对象的原始指针,以及一个引用计数(通常是一个整型)。
    • 当一个新的 shared_ptr 被创建并指向同一个对象时,引用计数会增加。
    • shared_ptr 被销毁或重置(reset)时,引用计数会减少。
    • 当引用计数变为零时,表明没有任何 shared_ptr 实例再指向该对象,自动释放该对象的内存。
  2. 控制块(Control Block)

    • shared_ptr 通常会管理一个控制块,除了一些其他信息(如自定义删除器),还保存引用计数(强引用计数和弱引用计数)。
    • 控制块存储了与所管理对象相关的所有信息,包括对对象的原始指针的引用。
  3. 线程安全

    • shared_ptr 的引用计数更新是线程安全的,允许在多线程环境中安全地共享 shared_ptr
  4. 拷贝和赋值

    • 当复制一个 shared_ptr 时,引用计数会增加,新的 shared_ptr 和原有的指针共同拥有同一个对象。
    • 赋值操作会首先检查新指针是否与旧指针相同,若不同则减少旧指针的引用计数并可能删除对象。

shared_ptr的一些限制:

  • shared_ptr的循环链表会导致资源泄漏,使用weak_ptr可以打破循环
  • 比起限定作用域的对象,共享所有权的对象会保持更长时间的活跃(因此会导致更高的平均资源占用)
  • 多线程环境中的共享指针代价很高(因为要保证线程安全,要防止使用计数上的数据竞争)
  • 共享对象析构函数的执行时间不可预测,共享对象的更新算法/逻辑比普通对象的相应算法/逻辑更容易出错。
  • 如果单一(最后的)结点保持一个大数据结构活跃,释放它所导致的析构函数层叠调用会导致严重的“垃圾收集延迟”,不利于实时响应。

当可以选择时:

  • 优先选择unique_ptr而不是shared_ptr
  • 优先选择普通限域对象而不是在堆中分配空间、由unique_ptr管理所有权的对象。

weak_ptr

weak_ptr指向一个shared_ptr所管理的对象。为了访问对象,可使用成员函数lock()将weak_ptr转换为shared_ptr。weak_ptr允许访问他人拥有的对象:

  • 仅当对象存在时你才需要访问它
  • 对象可能在任何时间被(其他人)释放
  • 在对象最后一次被使用后必须调用其析构函数(通常释放非内存资源)

可以将一个weak_ptr理解为两个指针:一个指针指向(可能是共享的)对象,另一个指针指向此对象的shared_ptr的使用计数:
在这里插入图片描述
若使用计数器用来保持使用计数结构活跃,因为在对象的最后一个shared_ptr的声明期结束后,仍可能有weak_ptr活跃。
对weak_ptr而言,为了访问它的对象,必须将它转换成shared_ptr。

std::weak_ptr 的作用

std::weak_ptr 是一种智能指针,它不影响被管理对象的引用计数。它提供了一种解决 std::shared_ptr 潜在问题的有效方式:

  • 打破循环引用: 在有多个 std::shared_ptr 相互引用的情况下,使用 std::weak_ptr 可以避免循环引用的产生。例如,假设有两个类 A 和 B,每个类都有一个指向对方的 std::shared_ptr。如果 A 持有 B 的 std::shared_ptr,而 B 也持有 A 的 std::shared_ptr,那么它们的引用计数将永远大于零。此时,如果把 B 的指针改为 std::weak_ptr,则 B 只在 A 使用它时引用 A,不会导致引用计数增加,从而允许它们的内存最终被释放。
  • 观察者模式: std::weak_ptr 常用于实现观察者模式。可以有多个观察者持有一个对象的 std::weak_ptr,而这个对象的生命期并不依赖于它们。这样,当被观察的对象被销毁时,观察者不会阻止它的销毁。
  • 避免悬空指针: 使用 std::weak_ptr 可以安全地检查和使用对象。你可以通过调用 lock() 方法来获得一个 std::shared_ptr,这个方法会返回一个有效的 shared_ptr,如果底层对象已被释放,则返回一个空的 shared_ptr。这机制确保了在使用被指向的对象时,能够有效地避免悬空指针导致的问题。

如何使用智能指针

在C++中,使用智能指针的情况通常包括以下几种:

  • 动态内存管理:当你使用new运算符创建对象时,智能指针可以帮助自动管理内存,避免内存泄露。在对象的生命周期结束时,智能指针会自动释放内存。、
  • 异常安全性:智能指针确保在发生异常时,资源能够被正确释放。使用原始指针时,如果在delete之前抛出了异常,可能会导致内存泄露。
  • 共享所有权:当多个对象或函数需要共享一个对象的所有权时,可以使用std::shared_ptr。这样可以确保对象在最后一次引用被释放时才会被销毁。
  • 独占所有权:当你希望某个对象只能有一个拥有者时,可以使用std::unique_ptr。这可以防止意外的拷贝和共享,确保资源的唯一性。
  • 自定义删除器:如果你需要在删除对象时执行特定的操作,可以使用智能指针的自定义删除器功能。例如,std::unique_ptr可以接受一个自定义的删除器函数。
  • 简化代码:智能指针的使用可以减少手动管理内存的代码,使得代码更加简洁和易于维护。
  • 避免悬挂指针:因为智能指针会在退出作用域时自动释放其管理的资源,避免了悬挂指针的风险。

综上所述,在需要动态管理资源、确保内存安全、简化内存管理代码的情况下,应优先考虑使用智能指针而不是原始指针。

参考

auto_ptr
weak_ptr
shared_ptr
unique_ptr

cpp11新特性之智能指针(上):关于auto_ptr的一切
cpp11新特性之智能指针(下):深入理解现代cpp中的智能指针shared_ptr、unique_ptr 以及 weak_ptr

posted @ 2025-02-22 15:32  main_c  阅读(1)  评论(0)    收藏  举报  来源