智能指针 学习笔记

简介

智能指针是 C++ 用于解决野指针、悬空指针、内存泄漏等问题的内存管理工具,位于头文件 <memory> 中。

野指针:未初始化的指针,非 nullptr
悬空指针:指向已释放空间的指针

其中目前还在使用的 unique_ptr/shared_ptr/weak_ptrC++11 引入。C++14 后又有一些相关函数引入,如 std::make_unique() 等。C++20 开始支持 std::atomic<std::shared_ptr<T>>,可以原子地操作智能指针。

智能指针的特性:

  • 遵循 RAII 的原则,在构造函数中获取资源,在析构函数中释放资源
  • 在初始化时会进行空指针检查,避免产生野指针
  • 使用引用计数管理资源,当计数器归零时自动释放资源,避免内存泄漏或悬空指针

std::unique_ptr

  • 独占式智能指针,即每个资源同时最多只能被一个 unique_ptr 拥有
  • 禁止拷贝,删除了拷贝构造函数和拷贝赋值运算符,防止了多个对象同时拥有一个资源
  • 支持移动语义,可以用移动语义转移资源的所有权,源指针将被置为 nullptr

std::shared_ptr

  • 共享式智能指针,基于引用计数管理共享资源,常在多线程编程中使用
  • 包含一个指向内存空间的指针和一个指向资源控制块的指针
  • shared_ptr 不直接支持数组管理,如果指向数组需要自定义删除器

std::weak_ptr

  • 弱引用智能指针,为辅助 shared_ptr 解决循环引用问题而引入

    循环引用:智能指针的指向关系形成一个环,导致程序结束时资源无法释放,造成内存泄漏
    弱引用:不会计入引用次数的引用,允许在必要时释放内存。通常是子结点对祖先结点的引用。

  • 不具有普通指针的功能,不具有资源的访问权,不影响引用计数,只表明一种临时的所有权

  • 可以由 shared_ptr 构造;在需要行使指针行为时需要先尝试调用 weak_ptr::lock() 创建一个 shared_ptr

std::auto_ptr

auto_ptr 是早期的独占式智能指针,自 C++98 出现,在 C++17 中已被移除。

auto_ptr 的拷贝语义会转移内存的所有权,使源指针悬空,这一特性不符合人的直觉,且在与 STL 容器配合时会带来风险。同时,auto_ptr 也无法管理动态数组。因此 auto_ptr 后来被弃用并移除。

常见问题

shared_ptr 的线程安全问题

  • shared_ptr 资源控制块的修改(如引用计数器的增减)是原子操作,线程安全
  • 多线程读写同一个 shared_ptr 不是线程安全的,因为指向修改不是原子的,包含几个步骤:内容指针移动、原目标引用计数减一、控制指针移动、新目标引用计数加一
  • 多线程读写共享同一资源的不同 shared_ptr 是线程安全的,因此通常按值传参而非按引用
  • 多线程通过 shared_ptr 读写同一资源不是线程安全的,需要加锁

按值传递/按引用传递

  • unique_ptr 由于禁止了拷贝,不能按值传递,通常按引用传递,也可以使用移动语义
  • shared_ptr 向线程传递时通常按值传递,否则容易出现线程安全问题

慎用裸指针构造/获取裸指针

  • 用裸指针构造智能指针或获取智能指针的裸指针后,程序既可以从智能指针访问资源,又可以从裸指针访问资源,存在重复析构资源的风险
  • 同理,不应把 this 指针作为 shared_ptr 返回。作为替代,可以继承 std::enable_shared_from_this 类并返回 std::shared_from_this()

作者

Cu_OH_2

发布于

2024-05-03

更新于

2024-06-19

许可协议