【Rust自学】15.5. Rc<T>:引用计数智能指针与共享所有权

喜欢的话别忘了点赞、收藏加关注哦,对接下来的教程有兴趣的可以关注专栏。谢谢喵!(=・ω・=)
请添加图片描述

15.5.1. 什么是Rc<T>

所有权在大部分情况下都是清晰的。对于一个给定的值,程序员可以准确地推断出哪个变量拥有它。

但是在某些场景中,单个值也可能同时被多个所有者持有,如下图:
请添加图片描述

在这个图数据结构中,其中的每个节点都有多条边指向它,所以这些节点从概念上讲就是同时属于所以指向它的边。而一个节点只要还有边指向它时就不应该被清理掉。这就是一种多重所有权。

为了支持多重所有权,Rust提供了Rc<T>类型,Rc是Reference counting(引用计数)的简写,这个类型会在实例的内部维护一个用于记录值的引用次数的计数器,从而判断这个值是否仍在使用。如果这个值的引用数量为0,那么这个值就可以被安全地清理掉了,而且不会触发引用实效的问题。

15.5.2. Rc<T>使用场景

当你希望将堆上的一些数据分享给程序的多个部分使用,但是在编译时又无法确定到底是程序的哪个部分最后使用完这些数据时,就可以使用Rc<T>

相反的,如果我们能在编译时确定程序的哪个部分会最后使用数据,那么只需要让这部分代码成为数据的所有者即可。这样依靠编译时的所有权规则就可以保证程序的正确性了。

需要注意的是,Rc<T>只能用于单线程场景,在以后的文章会研究如何在多线程中使用引用计数。

15.5.3. Rc<T>使用例

在使用前需要注意,Rc<T>不在预导入模块里,想要使用得先手动导入。

Rc下有这么一些基本的函数:

  • Rc::clone(&a)函数可以增加引用计数
  • Rc::strong_count(&a)可以获得引用计数,而且是强引用的计数
  • 既然有强引用,那就会有弱引用,也就是Rc::weak_count函数

用个例子来探究Rc<T>的实际应用:

一共有3个List,分别是abc。其中bc共享a。其余信息如图:
请添加图片描述

enum List {  Cons(i32, Box<List>),  Nil,  
}  use List::{Cons, Nil};fn main() {// main函数里换行只是为了链表结构更清晰,不是必要let a = Cons(5,  Box::new(Cons(10,  Box::new(Nil))));  let b = Cons(3,  Box::new(a));  let c = Cons(4,  Box::new(a));  
}
  • 首先创建了一个链表List,其写法在 15.1. 使用Box<T>来指向堆内存上的数据 中就有详细解释,这里不在阐述
  • main函数中先把a的结构写出来
  • 然后把bc的第一层写出来,嵌套的下一层直接写a即可。

逻辑没有问题,运行一下试试:

error[E0382]: use of moved value: `a`--> src/main.rs:17:27|
10 |     let a = Cons(5,|         - move occurs because `a` has type `List`, which does not implement the `Copy` trait
...
15 |                  Box::new(a));|                           - value moved here
16 |     let c = Cons(4,
17 |                  Box::new(a));|                           ^ value used here after move

报错内容是使用了已移动的值。这是因为在写b时写道了a所以a的所有权就被移到b里了。

这该怎么改呢?

一种办法是修改List的定义,让Cons持有引用而不是所有权,并且要为它指定对应的生命周期参数,但这个生命周期参数会要求List中所有元素的存活时间至少要和List本身一样。借用检查器会阻止我们编译这样的代码:

let a = Cons(10, &Nil);

Nil是一个零大小(zero-sized)的枚举变体,但是在表达式Cons(10, &Nil)&Nil中,编译器会把它视作一个临时值,这个临时值通常只在当前语句(或更小的作用域)里生效,之后就被自动丢弃。

简单地来说,&Nil是个临时变量,用完就被销毁,生命周期比enum短。临时创建的Nil的变体值会在a取得其引用前就被丢弃。

正确的方法是使用Rc<T>,用引用计数智能指针来让多个所有者共享同一块堆上的数据,并且在所有者都不用后自动释放内存:

enum List {  Cons(i32, Rc<List>),  Nil,  
}  use List::{Cons, Nil};  
use std::rc::Rc;  fn main() {  // main函数里换行只是为了链表结构更清晰,不是必要  let a = Rc::new(Cons(5,   Rc::new(Cons(10,   Rc::new(Nil)))));  let b = Cons(3,  Rc::clone(&a));  let c = Cons(4,  Rc::clone(&a));  
}

在声明bc时,使用Rc::clone并把a的引用&a作为参数传进去,这样bc就不会获得a的所有权,同时每使用一次Rc::clone就会把智能指针内的引用计数加1。

创建a时使用Rc::new算第一次引用,此时计数器为1;在bc中各使用了Rc::clone一次,引用计数就会各加1,最终引用计数就是3。a这个智能指针中的数据只有在引用计数为0时才会被清理掉。

其实在Rc<T>上也有clone方法(不是Clone trait的上的clone方法),其源码与Rc::clone完全一样,所以在给bc赋值时写a.clone()也是可以的。但因为这么写可能会被误解为深拷贝(尤其是对新手来说),而实际它只是增加了引用计数,所以不推荐这么写,更多还是使用Rc::clone

接下来我们修改一下main函数,打印一些帮助信息,看看当c超出范围时引用计数如何变化:

fn main() {let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));println!("count after creating a = {}", Rc::strong_count(&a));let b = Cons(3, Rc::clone(&a));println!("count after creating b = {}", Rc::strong_count(&a));{let c = Cons(4, Rc::clone(&a));println!("count after creating c = {}", Rc::strong_count(&a));}println!("count after c goes out of scope = {}", Rc::strong_count(&a));
}

这里c会比ab先走出作用域,所以在c走出作用域后引用计数会减1。

输出:

count after creating a = 1 
count after creating b = 2 
count after creating c = 3 
count after c goes out of scope = 2

在此示例中我们看不到的是,当bamain末尾超出范围时,计数为 0,并且Rc<List>被完全清理。

因为Rc<T>实现了Drop trait,所以当Rc<T>离开作用域时引用计数器会自动减1。使用Rc<T>允许单个值拥有多个所有者,并且计数可确保只要任何所有者仍然存在,该值就保持有效。

15.5.4. Rc<T>总结

Rc<T>通过不可变引用,使程序员可以在程序的不同部分之间共享只读的数据。

这里再次强调,Rc<T>引用是不可变的,如果Rc<T>允许程序员持有多个可变引用的话就会违反借用规则(详见 4.4. 引用与借用)——多个指向同一区域的可变引用会导致数据竞争以及数据的不一致。

而在实际开发中肯定会遇到需要数据可变的情况,针对它Rust提供了内部可变性模式和RefCell<T>,程序员可以将其与Rc<T>结合使用来处理此不变性限制。下一篇文章会讲到。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/10622.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

UE5制作视差图

双目深度估计开源数据集很多都是用UE制作的&#xff0c;那么我们自己能否通过UE制作自己想要的场景的数据集呢。最近花了点时间研究了一下&#xff0c;分享给需要的小伙伴。 主要使用的是UnrealCV插件&#xff0c;UnrealCV是一个开源项目&#xff0c;旨在帮助计算机视觉研究人…

基于遗传优化GRNN和Hog特征提取的交通标志识别算法matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 4.1 HOG 4.2 GRNN&#xff08;General Regression Neural Network&#xff09;模型原理 4.3 遗传算法&#xff08;GA&#xff09;优化GRNN平滑因子 5.算法完整程序工程 1.算法运行效果图预…

C语言【基础篇】之流程控制——掌握三大结构的奥秘

流程控制 &#x1f680;前言&#x1f99c;顺序结构&#x1f4af; 定义&#x1f4af;执行规则 &#x1f31f;选择结构&#x1f4af;if语句&#x1f4af;switch语句&#x1f4af;case穿透规则 &#x1f914;循环结构&#x1f4af;for循环&#x1f4af;while循环&#x1f4af;do -…

C++实现状态模式

首先上代码&#xff1a; #include <iostream> #include <memory>class Context;class State { public:virtual void Handle(Context * context) 0; //纯虚函数virtual ~State() default; //虚析构函数 };//创建状态A class ConcreateStateA : public State{…

【React】PureComponent 和 Component 的区别

前言 在 React 中&#xff0c;PureComponent 和 Component 都是用于创建组件的基类&#xff0c;但它们有一个主要的区别&#xff1a;PureComponent 会给类组件默认加一个shouldComponentUpdate周期函数。在此周期函数中&#xff0c;它对props 和 state (新老的属性/状态)会做一…

二级C语言:二维数组每行最大值与首元素交换、删除结构体的重复项、取出单词首字母

目录 一、程序填空 --- 二维数组每行最大值与首元素交换 题目 分析 知识点 --- 交换语句 二、程序修改 --- 删除结构体的重复项 题目 分析 三、程序设计 --- 取出单词首字母 题目 分析 前言 本章讲解&#xff1a;二维数组每行最大值与首元素交换、删除结构体的重复项…

CUDA学习-内存访问

一 访存合并 1.1 说明 本部分内容主要参考: 搞懂 CUDA Shared Memory 上的 bank conflicts 和向量化指令(LDS.128 / float4)的访存特点 - 知乎 1.2 share memory结构 图1.1 share memory结构 放在 shared memory 中的数据是以 4 bytes(即 32 bits)作为 1 个 word,依…

【python】python基于机器学习与数据分析的手机特性关联与分类预测(源码+数据集)【独一无二】

&#x1f449;博__主&#x1f448;&#xff1a;米码收割机 &#x1f449;技__能&#x1f448;&#xff1a;C/Python语言 &#x1f449;专__注&#x1f448;&#xff1a;专注主流机器人、人工智能等相关领域的开发、测试技术。 python基于机器学习与数据分析的手机特性关联与分类…

【leetcode详解】T3175(一点反思)

解题心得 要写出一个好的程序&#xff0c;有效解决问题&#xff0c;思路上就不能“太乖” —— 不能被题目的叙述过程所束缚&#xff0c;而是力求细思问题&#xff0c;抽象化问题&#xff0c;并找到背后的逻辑&#xff1b;最后抓住核心对象&#xff0c;去除多余项&#xff0c;…

图论——最小生成树

最小生成树 给定一个无向图&#xff0c;在图中选择若干条边把图的所有节点连起来。要求边长之和最小。在图论中&#xff0c;叫做求最小生成树。 prim算法 prim 算法采用的是一种贪心的策略。 每次将离连通部分的最近的点和点对应的边加入的连通部分&#xff0c;连通部分逐渐扩大…

jvisualvm工具使用

jvisualvm 是JDK自带的具有图形界面操作功能的JVM性能监控和诊断工具&#xff0c;它不仅能分析和诊断堆转储文件&#xff0c;在线实时监控本地JVM进程&#xff0c;还能监控远程服务器上的JVM进程。 1 分析服务器下载dump文件 1&#xff09;在我们在安装JDK的bin目录双击jvisa…

C++ list

list需知&#xff1a; list不会出现insert迭代器失效问题 链表插入不会影响原有数据相对位置&#xff0c;且不用扩容 但是erase会导致相对数据位置移动&#xff0c;所有其erase会导致迭代器失效 list排序效率很低 不建议使用 小规模数据量可以使用&#xff0c;比较方便 此外…

DeepSeek-R1 论文解读 —— 强化学习大语言模型新时代来临?

近年来&#xff0c;人工智能&#xff08;AI&#xff09;领域发展迅猛&#xff0c;大语言模型&#xff08;LLMs&#xff09;为通用人工智能&#xff08;AGI&#xff09;的发展开辟了道路。OpenAI 的 o1 模型表现非凡&#xff0c;它引入的创新性推理时缩放技术显著提升了推理能力…

【基于SprintBoot+Mybatis+Mysql】电脑商城项目之用户注册

&#x1f9f8;安清h&#xff1a;个人主页 &#x1f3a5;个人专栏&#xff1a;【计算机网络】【Mybatis篇】 &#x1f6a6;作者简介&#xff1a;一个有趣爱睡觉的intp&#xff0c;期待和更多人分享自己所学知识的真诚大学生。 目录 &#x1f3af;项目基本介绍 &#x1f6a6;项…

蓝桥杯思维训练营(一)

文章目录 题目总览题目详解翻之一起做很甜的梦 蓝桥杯的前几题用到的算法较少&#xff0c;大部分考察的都是思维能力&#xff0c;方法比较巧妙&#xff0c;所以我们要积累对应的题目&#xff0c;多训练 题目总览 翻之 一起做很甜的梦 题目详解 翻之 思维分析&#xff1a;一开…

【AI】DeepSeek 概念/影响/使用/部署

在大年三十那天&#xff0c;不知道你是否留意到&#xff0c;“deepseek”这个词出现在了各大热搜榜单上。这引起了我的关注&#xff0c;出于学习的兴趣&#xff0c;我深入研究了一番&#xff0c;才有了这篇文章的诞生。 概念 那么&#xff0c;什么是DeepSeek&#xff1f;首先百…

minimind - 从零开始训练小型语言模型

大语言模型&#xff08;LLM&#xff09;领域&#xff0c;如 GPT、LLaMA、GLM 等&#xff0c;虽然它们效果惊艳&#xff0c; 但动辄10 Bilion庞大的模型参数个人设备显存远不够训练&#xff0c;甚至推理困难。 几乎所有人都不会只满足于用Lora等方案fine-tuing大模型学会一些新的…

【机器学习】自定义数据集 使用pytorch框架实现逻辑回归并保存模型,然后保存模型后再加载模型进行预测,对预测结果计算精确度和召回率及F1分数

一、使用pytorch框架实现逻辑回归 1. 数据部分&#xff1a; 首先自定义了一个简单的数据集&#xff0c;特征 X 是 100 个随机样本&#xff0c;每个样本一个特征&#xff0c;目标值 y 基于线性关系并添加了噪声。将 numpy 数组转换为 PyTorch 张量&#xff0c;方便后续在模型中…

数据分析系列--④RapidMiner进行关联分析(案例)

一、核心概念 1.项集&#xff08;Itemset&#xff09; 2.规则&#xff08;Rule&#xff09; 3.支持度&#xff08;Support&#xff09; 3.1 支持度的定义 3.2 支持度的意义 3.3 支持度的应用 3.4 支持度的示例 3.5 支持度的调整 3.6 支持度与其他指标的关系 4.置信度&#xff0…

国产之光DeepSeek架构理解与应用分析

目录 初步探索DeepSeek的设计 一、核心架构设计 二、核心原理与优化 三、关键创新点 四、典型应用场景 五、与同类模型的对比优势 六、未来演进方向 从投入行业生产的角度看 一、DeepSeek的核心功能扩展 二、机械电子工程产业中的具体案例 1. 预测性维护&#xff08;Predictive…