【Rust】16. 智能指针

  • 智能指针(smart pointers)是一类数据结构,他们的表现类似指针,但是也拥有额外的元数据和功能
  • 引用是一类只借用数据的指针;相反,在大部分情况下,智能指针拥有他们指向的数据

在这里插入图片描述

16.1 Box<T>:指向堆上的数据

  • box 允许将一个值放在堆上而不是栈上,box 是一个本身留在栈上但指向堆数据的指针
  • box 的作用:提供固定大小、提供堆分配、间接存储

在这里插入图片描述

16.1.1 使用 Box<T> 在堆上储存数据

  • box:在离开作用域时,将被释放,释放过程作用于 box 本身(位于栈上)和它所指向的数据(位于堆上)

在这里插入图片描述

16.1.2 Box 的应用:创建递归类型

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

16.1.3 计算非递归类型的大小

  • enum:实际上只会使用其中的一个成员,所以枚举值所需的空间等于储存其最大成员的空间大小

在这里插入图片描述

16.1.4 使用 Box<T> 给递归类型一个已知的大小

  • Box<T> 是一个指针,我们总是知道它需要多少空间:指针的大小并不会根据其指向的数据量而改变
  • 核心思想:建议中的 “indirection” 意味着不同于直接储存一个值,应该间接的储存一个指向值的指针!
  • 通过使用 box,打破了这无限递归的连锁,这样编译器就能够计算出储存 List 值需要的大小了

在这里插入图片描述
在这里插入图片描述

16.2 通过 Deref trait 将智能指针当作常规引用处理

  • 实现 Deref trait 的智能指针可以被当作常规引用来对待,可以编写操作引用的代码并用于智能指针

在这里插入图片描述

16.2.1 解引用 *:追踪指针的值

  • 解引用运算符 * 可用于追踪引用 & 所指向的值

在这里插入图片描述

16.2.2 像引用一样使用 Box<T>

  • Box<T> 可用于生成拷贝一个值的引用(注意是引用,而不是值)的实例(而不是指向该值的引用,因为生成的是 Box 引用,不是常规引用,二者不可比

在这里插入图片描述

16.2.3 自定义智能指针(一):引入

在这里插入图片描述

16.2.4 自定义智能指针(二):实现 Deref trait 将某类型像引用一样处理

  • 对于实现 Deref trait 的数据类型,要求实现 deref 方法,该方法借用 self 并返回一个内部数据的引用、并可用于后续的解引用
  • 没有 Deref trait 的话,编译器只会解引用 & 引用类型
  • *y 的底层运行为 *(y.deref()):Rust 将 * 运算符替换为先调用 deref 方法再进行普通解引用的操作

在这里插入图片描述

16.2.5 函数和方法的隐式 Deref 强制转换

  • Deref 强制转换(deref coercions)将实现了 Deref trait 的类型的引用转换为另一种类型的引用
  • 当这种特定类型的引用作为实参传递给和形参类型不同的函数或方法时将自动进行,这时会有一系列的 deref 方法被调用,把我们提供的类型转换成了参数所需的类型

在这里插入图片描述

16.2.6 Deref 强制转换如何与可变性交互

  • Deref trait 重载不可变引用* 运算符;DerefMut trait 用于重载可变引用的 * 运算符
  • Rust 在发现类型和 trait 实现满足三种情况时会进行 Deref 强制转换,如下所示:
    1. T: Deref<Target=U> 时从 &T&U:如果有一个 &T,而 T 实现了返回 U 类型的 Deref,则可以直接得到 &U
    1. T: DerefMut<Target=U> 时从 &mut T&mut U:与 1 类似(同上)
    1. T: Deref<Target=U> 时从 &mut T&URust 也会将可变引用强转为不可变引用,但与之相反的,不可变引用永远也不能强转为可变引用!

在这里插入图片描述

16.3 Drop Trait:运行清理代码

16.3.1 Drop Trait 的基本概念

在这里插入图片描述
在这里插入图片描述

16.3.2 std::mem::drop:提前丢弃值

  • std::mem::drop 可显式调用来丢弃值,该方法已经存在于 prelude 中
  • 所有权系统确保引用总是有效的,也会确保 drop 只会在值不再被使用时被调用一次

在这里插入图片描述
在这里插入图片描述

16.4 Rc<T>:引用计数智能指针

  • 引用计数(reference counting):意味着记录一个值引用的数量来知晓这个值是否仍在被使用,如果某个值有零个引用,就代表没有任何有效引用并可以被清理
  • 如果确实知道哪部分是最后一个结束使用的话,就可以令其成为数据的所有者,正常的所有权规则就可以在编译时生效
  • 为了启用多所有权需要显式地使用 Rust 类型 Rc<T>,注意 Rc<T> 只能用于单线程场景

在这里插入图片描述

16.4.1 使用 Rc<T> 共享数据

  • Rc<T> 可通过克隆的方式 Rc::clone 来增加引用计数,直到有零个引用之前其数据都不会被清理
  • Rc::clone 的实现并不像大部分类型的 clone 实现那样对所有数据进行深拷贝,只会增加引用计数,这并不会花费多少时间,可以明显的区别深拷贝类的克隆和增加引用计数类的克隆(在下面的示例中也可以调用 a.clone()

在这里插入图片描述
在这里插入图片描述

16.4.2 克隆 Rc 会增加引用计数

  • Rc::strong_count:返回引用计数的值

在这里插入图片描述

16.5 RefCell<T> 和内部可变性模式

  • 内部可变性(Interior mutability)是 Rust 中的一个设计模式,它允许即使在有不可变引用时也可以改变数据,这通常是借用规则所不允许的。为了改变数据,该模式在数据结构中使用 unsafe 代码来模糊 Rust 通常的可变性和借用规则

在这里插入图片描述

16.5.1 RefCell<T>:在运行时检查借用规则

  • 对于引用和 Box<T>:借用规则的不可变性作用于编译时;如果违反这些规则会得到一个编译错误
  • 对于 RefCell<T>:这些不可变性作用于运行时;而对于 RefCell<T>,如果违反这些规则程序会 panic 并退出

在这里插入图片描述

16.5.2 内部可变性:不可变值的可变借用

  • 借用规则推论:对于一个不可变值,不能可变的借用它

在这里插入图片描述

16.5.3 内部可变性的示例:mock 对象

  • std::cell::RefCell:内部可变性相关模块
  • 在下面的示例中,对于要修改的结构体字段 sent_messages 字段的类型是 RefCell<Vec<String>> 而不是 Vec<String>;在 new 函数中新建了一个 RefCell<Vec<String>> 实例替代空 vector
  • RefCell::borrow_mut 方法来获取 RefCell 中值的可变引用;RefCell::borrow 方法来获取 RefCell 中值的不可变引用

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

16.5.4 RefCell<T>:在运行时记录借用

  • 不可变引用:一般使用 & 语法;对于 RefCell<T> 使用 borrow 方法,返回一个 Ref<T> 类型的智能指针,且实现了 Deref trait
  • 可变引用:一般使用 &mut 语法;对于 RefCell<T> 使用 borrow_mut 方法,返回一个 RefMut<T> 类型的智能指针,且实现了 Deref trait
  • RefCell<T> 记录当前有多少个活动的 Ref<T>RefMut<T> 智能指针
  • 每次调用 borrowRefCell<T> 将活动的不可变借用计数加一;当 Ref<T> 值离开作用域时,不可变借用计数减一
  • 像编译时借用规则一样,RefCell<T> 在任何时候只允许有多个不可变借用或一个可变借用
  • 如果违反借用规则,相比引用时的编译时错误,RefCell<T> 的实现会在运行时出现 panic

在这里插入图片描述
在这里插入图片描述

16.5.5 结合 Rc<T> 和 RefCell<T> 来拥有多个可变数据所有者

在这里插入图片描述
在这里插入图片描述

16.6 引用循环与内存泄漏

在这里插入图片描述

16.6.1 制造引用循环

  • 创建引用循环并不容易,但也不是不可能:如果你有包含 Rc<T>RefCell<T> 值或类似的嵌套结合了内部可变性和引用计数的类型,请小心检查有没有形成一个引用循环
  • 另一个解决方案是重新组织数据结构,使得一部分引用拥有所有权而另一部分没有(换句话说,循环将由一些拥有所有权的关系和一些无所有权的关系组成,只有所有权关系才能影响值是否可以被丢弃)
  • 代码解析:*link.borrow_mut() = Rc::clone(&b); 表示将取出的变量 link 引用通过 borrow_mut() 方法来获得其可变引用,再用 * 来解引用指针,解引用后将这个可变引用重新指向 b 的克隆引用(*link.borrow_mut() 应该等价于 *(link.borrow_mut())

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

16.6.2 避免引用循环:将 Rc<T> 变为 Weak<T>

  • 创建弱引用(weak reference):调用 Rc::downgrade 并传递 Rc<T> 实例的引用来创建其值的弱引用,并会得到 Weak<T> 类型的智能指针,会将 weak_count 加 1。创建弱引用的例子:*leaf.parent.borrow_mut() = Rc::downgrade(&branch);
  • 弱引用的 weak_count:用来记录存在多少个 Weak<T> 引用,与 strong_count 的区别在于,weak_count 无需计数为 0 就能使 Rc<T> 实例被清理
  • 使用 upgrade 方法用于判断指向的值是否被丢弃:因为 Weak<T> 引用的值可能已经被丢弃了,为了使用 Weak<T> 所指向的值,我们必须确保其值仍然有效,为此可以调用 Weak<T> 实例的 upgrade 方法,这会返回 Option<Rc<T>>
  • 强、若引用的区别:强引用代表如何共享 Rc<T> 实例的所有权(引用并拥有),但弱引用并不属于所有权关系(仅引用,但不拥有)!
  • 弱引用不会造成引用循环,因为任何弱引用的循环会在其相关的强引用计数为 0 时被打断

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

16.7 小结

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值