• 忘掉天地
  • 仿佛也想不起自己
bingliaolongBingliaolong  2024-08-31 13:31 Aet 隐藏边栏 |   抢沙发  4 
文章评分 2 次,平均分 5.0

shared_ptr

概述

  1. C++ 标准库中的一种智能指针,用于自动管理动态分配的对象的生命周期
  2. 主要机制是通过引用计数(Reference Counting)来追踪有多少个 shared_ptr 实例共享管理同一个对象
    1. 当引用计数降为零时,对象会被自动释放

内部组成

  1. 指向管理对象的指针
    1. 用于直接访问实际的资源对象
  2. 控制块
    1. 控制块存储了引用计数等重要信息,用于管理对象的生命周期

控制块

  1. 引用计数
    1. 跟踪有多少个 shared_ptr 指向该对象
  2. 弱引用计数
    1. 用于追踪有多少个 weak_ptr 引用控制块
    2. 即使 use_count 变为 0,控制块依然存在,直到 weak_count 也降为 0 时才释放
  3. 管理对象的指针
    1. 指向实际的对象,shared_ptr 通过它访问对象
  4. 自定义删除器
    1. 可选的,用户定义的资源释放方式,如使用 std::freedelete、自定义函数等

控制块生命周期

  1. 控制块的生命周期与弱引用计数相关,只有当 weak_count 也降为 0 时,控制块才会被释放
  2. 这允许 weak_ptr 在共享对象销毁后仍能检测对象是否已经被销毁

引用计数管理

  1. 加引用计数:
    1. shared_ptr 被复制构造或赋值时,use_count 增加
  2. 减少引用计数:
    1. shared_ptr 被销毁、赋值或重置时,use_count 减少
  3. 对象释放:
    1. use_count 降为 0 时,shared_ptr 会调用 Deleter 释放对象
    2. 此时,use_count为0,如果weak_count也为0,也就是没有weak_ptr对象指向shared_ptr,控制器才会被释放

线程安全

  1. 引用计数的增加和减少是线程安全的
  2. 对象的删除操作由引用计数降为零的线程执行,本身是线程安全的
  3. 但析构函数内部的操作需要手动保证线程安全

enable_shared_from_this

  1. 概述
    1. 用来解决对象内部获取其 shared_ptr 的工具,它允许类从内部获得指向自己的 shared_ptr,并确保不会创建新的控制块
  2. 理解
    1. MyClass的某个对象对应的所有shared_ptr对象,共用了同一个控制器
    2. MyClass析构函数还是需要手动确保线程安全
  3. 好处
    1. 避免资源浪费
    2. 确保引用计数同步

转为shared_ptr

  1. 可以通过 std::moveunique_ptr 转换为 shared_ptr
  2. 但反之不可

引用循环

  1. 概述
    1. shared_ptr 的引用循环(Reference Cycle)是一个常见的内存管理问题,尤其是在使用智能指针管理对象时
    2. 这种情况会导致内存泄漏,因为涉及到的对象之间相互持有 shared_ptr,使得引用计数永远无法降为零,从而导致对象无法被正确释放
  2. 产生
    1. 引用循环通常发生在两个或多个对象相互持有 shared_ptr 指向对方的情况下
    2. 这样的相互引用使得所有对象的引用计数都无法降为零,即使它们已经不再被程序的其他部分使用
  3. 引用循环示例代码:

  1. 解决上述引用循环的示例:

  1. 面对多个相互引用的类时:
    1. 至少保证在引用链中存在一个 weak_ptr 来打破循环,从而确保对象能够被正确释放

引用循环-观察者模式

  1. 使用shared_ptr保存了观察者,增加了引用计数,导致无法释放观察者

  1. 使用weak_ptr解决来保存观察者,解决问题

理解

  1. 为什么析构函数内部的操作需要手动保证线程安全
    1. shared_ptr 的引用计数在线程 X 中变为 0 时,它立即开始释放对象,调用对象的析构函数
    2. 此时其他的线程比如Y可能引用计数不为0,它可能正在操作或访问这个对象
    3. 于是就会出现访问已释放的对象的风险,导致未定义行为

weak_ptr

概述

  1. C++11 引入的一种智能指针,专为解决循环引用问题而设计
  2. 它是对 std::shared_ptr 的一种弱引用,避免了 shared_ptr 间的循环引用,能够有效防止内存泄漏
  3. weak_ptr 不参与引用计数的增加,因此不会影响对象的生命周期

内部组成

  1. 控制块
    1. weak_ptr 的核心在于与 shared_ptr 共享同一个控制块
    2. 控制块是 shared_ptrweak_ptr 共同管理对象生命周期的关键数据结构
  2. 指向控制块的指针
    1. weak_ptr 自身包含一个指向控制块的指针,而不直接包含指向对象的指针
    2. 通过指向控制块,weak_ptr 可以间接地了解对象的状态(是否被销毁),而不直接影响对象的生命周期
  3. 独立于对象的弱引用计数
    1. weak_count 用于跟踪有多少个 weak_ptr 引用控制块
    2. 即使 shared_ptruse_count 变为 0,控制块也不会立即销毁
    3. 只有当 weak_count 也变为 0 时,控制块才会被销毁
      这种设计保证了 weak_ptr 可以安全地检查对象是否已经被销毁,即使没有任何 shared_ptr 存在

控制块

  1. weak_ptr 和对应的 shared_ptr 使用的是同一个控制块
  2. 引用计数
    1. 跟踪有多少个 shared_ptr 指向该对象
  3. 弱引用计数
    1. 用于追踪有多少个 weak_ptr 引用控制块
  4. 管理对象的指针
    1. 指向实际的对象,shared_ptr 通过它访问对象
  5. 自定义删除器
    1. 可选的,用户定义的资源释放方式,如使用 std::freedelete、自定义函数等

线程安全

  1. 引用计数的增加和减少是线程安全的
  2. expired()方法是线程安全的
  3. lock()方法是线程安全的
  4. 直接访问 weak_ptr 本身的状态并非线程安全
    1. 比如两个线程同时对同一个 weak_ptr 调用 reset()、赋值、或修改等操作,
      这些并不是线程安全的,需要额外的同步机制(如互斥锁)来确保安全
    2. 当两个线程同时操作同一个 weak_ptr 时,可能导致一个线程正在析构对象,而另一个线程还在试图访问对象,这种情况就可能导致访问已释放的内存
    3. 例如,一个线程通过 weak_ptrlock() 获取了一个 shared_ptr,而此时另一个线程可能正在销毁这个对象的资源,这种情况可能导致未定义行为

工作原理

  1. 创建weak_ptr

  1. 访问控制块
    1. weak_ptr 可以通过控制块检查对象是否已被销毁,通过 expired() 方法检测 use_count 是否为 0
    2. weak_ptr 使用 lock() 方法可以获取一个新的 shared_ptr,如果 use_count 为 0,则 lock() 返回空的 shared_ptr
  2. 销毁控制块
    1. shared_ptruse_count 降为 0 时,控制块中的对象会被销毁,但控制块本身会继续存在,直到 weak_count 也降为 0
    2. 当所有 weak_ptr 也被销毁或 reset() 后,weak_count 变为 0,控制块才会被释放

shared_ptr 的关系

  1. 控制块的共享
    1. 你创建一个 shared_ptr 时,会创建一个控制块
      控制块中包含了 use_count(引用计数)和 weak_count(弱引用计数),以及对实际对象的指针和删除器
    2. 当从 shared_ptr 创建一个 weak_ptr 时,weak_ptr 并不会直接持有对对象的指针或 shared_ptr,而是通过控制块感知对象的状态
    3. weak_ptr 增加的是控制块的 weak_count,不会影响 shared_ptruse_count
  2. weak_ptr 保存的是控制块的指针
    1. weak_ptr 内部保存了一个指向 shared_ptr 所使用的控制块的指针
    2. 通过这个控制块,weak_ptr 可以了解对象是否存在,并在需要时通过 lock() 创建一个新的 shared_ptr 来安全访问对象

shared_ptr 的区别

  1. shared_ptr 直接持有对象的指针,并增加引用计数来管理对象的生命周期
    1. weak_ptr 不直接持有对象的指针,而是依赖于控制块中的引用计数来判断对象的状态
  2. weak_ptr 不能直接访问对象
    1. 而是需要通过 lock() 获取一个新的 shared_ptr 来安全地访问对象

expired

  1. 用于检查 weak_ptr 所指向的对象是否已经被销毁
  2. 返回 true:表示对象已经被销毁,shared_ptr 的引用计数为 0
  3. 返回 false:表示对象仍然存在,shared_ptr 的引用计数大于 0

lock

  1. lock() 方法返回一个新的 shared_ptr,指向 weak_ptr 所观测的对象
  2. 如果对象还存在(即 shared_ptr 的引用计数 use_count 大于 0),lock() 返回一个指向对象的 shared_ptr,这意味着对象还可以被安全使用
  3. 如果对象已被销毁(即 shared_ptr 的引用计数 use_count0),lock() 返回一个空的 shared_ptr,这表示对象已经被释放

场景

  1. 解决循环引用问题
    1. 常用于 Observer 模式、父子关系、双向关联的类中,防止相互持有导致的循环引用
  2. 缓存与共享资源的管理
    1. weak_ptr 常用于缓存系统中,用来跟踪某些对象是否仍在使用,而不强制延长对象的生命周期
  3. 安全监测对象的销毁
    1. weak_ptr 允许在对象已销毁时,避免非法访问,通过 lock() 的返回值检查对象是否依然存在

unique_ptr

概述

  1. C++11 引入的智能指针,用于管理动态分配的对象,确保对象的生命周期和内存管理更加安全和简洁
  2. 与其他智能指针不同,unique_ptr 是独占所有权的指针,即一个 unique_ptr 对象拥有其所指向的对象,且不能与其他 unique_ptr 共享所有权

用法

  1. 创建

  1. 移动
    1. unique_ptr 不允许复制,但支持移动
    2. 使用 std::move 将所有权转移给另一个 unique_ptr

  1. 使用自定义删除器
    1. 自定义删除器可以是函数指针、函数对象或 lambda 表达式,用于指定如何销毁对象

内部组成

  1. 原始指针
    1. unique_ptr 内部保存了一个原始指针(T*),指向由它管理的对象
    2. 这个指针是 unique_ptr 的核心,它直接持有对象的地址,并通过它来访问和操作对象
  2. 自定义删除器
    1. unique_ptr 支持自定义删除器,允许用户指定如何销毁和释放所管理的对象
    2. 删除器可以是默认的 delete 操作,也可以是用户定义的函数、函数对象或 lambda 表达式
  3. 指针与删除器的组合
    1. unique_ptr 通过一种名为“压缩对”(Compressed Pair)的优化技术,将原始指针和删除器合并在一起,以减少内存占用
    2. 如果删除器是空类(即没有成员变量的类),编译器可以优化它的存储,使得 unique_ptr 的大小仅为一个指针的大小

线程安全

  1. unique_ptr 是独占对象所有权的智能指针,不允许复制,只允许移动
    1. 这一设计确保了同一时间只有一个 unique_ptr 可以指向某个对象,因此不会有多个指针同时管理同一个对象的情况,从而避免了共享资源的复杂性
  2. 非线程安全的操作
    1. 操作 unique_ptr 本身并不线程安全
      例如,两个线程同时对同一个 unique_ptr 进行操作(如 reset()release()、或移动赋值)是非线程安全的,会导致数据竞争
    2. 对象的访问也不受保护
      unique_ptr 不会保护其所指对象的内部数据的线程安全性,如果多个线程访问对象,需要额外的同步措施来保证安全
  3. 线程安全的使用场景
    1. 在没有跨线程共享或修改 unique_ptr 本身的情况下,unique_ptr 是安全的
      例如,将 unique_ptr 移动到另一个线程中独占使用是安全的,因为没有多个线程同时操作同一个 unique_ptr 实例

安全的使用方式

  1. unique_ptr 移动到其他线程
    1. unique_ptr 可以安全地通过 std::move 转移到其他线程中,由新线程独占管理
    2. 这样保证了对象的所有权在一个线程中,不存在并发访问的风险

  1. 避免多线程共享 unique_ptr
    1. 不要尝试在多个线程间共享 unique_ptr 实例,除非明确使用了同步机制(如互斥锁)来保护对 unique_ptr 的访问
  2. 使用 std::mutex 保护 unique_ptr
    1. 如果必须在多线程中访问同一个 unique_ptr,需要使用 std::mutex 或其他同步工具来保证线程安全

工作原理

  1. 独占所有权
  2. 自动内存管理
  3. 自定义删除器
  4. 指针和删除器组合

shared_ptr 的区别

  1. unique_ptr:独占所有权,不可复制,只能移动
    1. shared_ptr:可以共享所有权,有引用计数,多个 shared_ptr 可以同时管理同一对象
  2. unique_ptr 没有引用计数,操作简单且性能高
    1. shared_ptr 有引用计数,稍微复杂,开销较大,但在需要共享所有权时非常有用

release

  1. release() 函数用于释放 unique_ptr 对象的所有权,不调用删除器,也不会销毁对象,而是返回原始指针
  2. unique_ptr 变为空

reset

  1. reset() 用于重置 unique_ptr,释放当前管理的对象(如果有),然后可选择性地用新对象替代管理

get

  1. get() 返回 unique_ptr 管理的原始指针,但不会转移所有权,主要用于访问对象

swap

  1. swap() 用于交换两个 unique_ptr 管理的对象,两个指针交换了其管理的对象及删除器

operator*operator->

  1. unique_ptr 重载了 *-> 操作符,用于访问所管理的对象的成员

operator bool

  1. 提供了到 bool 类型的隐式转换,可以用于检查指针是否为空

std::move

  1. 由于 unique_ptr 不能复制(拷贝构造和赋值被删除),但可以通过 std::move 转移所有权

注意事项

  1. 避免从 unique_ptr 中获取原始指针
    1. 虽然可以使用 get() 获取 unique_ptr 的原始指针,但这意味着绕过了智能指针的自动内存管理机制,使用时需要非常谨慎
  2. 不要使用原始指针构造多个 unique_ptr
    1. 从原始指针创建多个 unique_ptr 会导致同一对象被多次释放,导致未定义行为
  3. 合理使用 std::move
    1. 在需要转移所有权时,应使用 std::move,但要确保不会误用,导致对象所有权丢失

场景

  1. 资源管理
    1. unique_ptr 非常适合用于管理动态分配的内存、文件句柄、网络资源等,确保资源在不再需要时被正确释放
  2. RAII(资源获取即初始化)模式
    1. 使用 unique_ptr 可以实现 RAII 模式,确保资源在对象生命周期结束时自动释放
  3. 工厂函数的返回值
    1. unique_ptr 常用作工厂函数的返回值,提供一种安全且明确的所有权转移方式

chromium智能指针

scoped_ptr

  1. 概述
    1. 一种独占所有权的智能指针,与 std::unique_ptr 类似
    2. 设计目的是在作用域结束时自动销毁所管理的对象,以确保资源不会泄漏
    3. 由于 scoped_ptr 不支持移动操作(如 std::move),在 C++11 之后,它逐渐被 std::unique_ptr 所取代,并在新的 Chromium 代码中不再推荐使用
  2. 特点
    1. 拥有独占的对象所有权,不支持复制和赋值
    2. scoped_ptr 离开作用域时,自动调用对象的析构函数

scoped_refptr

  1. 概述

    1. Chromium 中的引用计数智能指针,类似于 std::shared_ptr,但它具有更轻量级的实现,主要用于管理具有内部引用计数的对象
    2. 适用于需要共享所有权的对象,特别是在多线程环境下常见的对象引用管理场景
  2. 特点

    1. 自动管理引用计数,在对象被所有指针释放后自动销毁
    2. 适用于具有 AddRef()Release() 方法的对象,常用于 COM 对象或自定义引用计数对象
    3. 支持复制和赋值,引用计数会自动调整
  3. 工作原理

    1. scoped_refptr 在构造时调用对象的 AddRef() 方法增加引用计数,在析构时调用 Release() 方法减少引用计数
    2. Release() 使引用计数归零时,自动调用对象的析构函数
  4. 示例

本文为原创文章,版权归所有,欢迎分享本文,转载请保留出处!

bingliaolong
Bingliaolong 关注:0    粉丝:0
Everything will be better.

发表评论

表情 格式 链接 私密 签到
扫一扫二维码分享