Rust中的智能指针是什么

智能指针(smart pointers)是一类数据结构,是拥有数据所有权和额外功能的指针。是指针的进一步发展

指针(pointer)是一个包含内存地址的变量的通用概念。这个地址引用,或 ” 指向”(points at)一些其 他数据 。引用以 & 符号为标志并借用了他们所 指向的值。除了引用数据没有任何其他特殊功能。它们也没有任何额外开销,所以在Rust中应用得最多。

智能指针是Rust中一种特殊的数据结构。它与普通指针的本质区别在于普通指针是对值的借用,而智能指针通常拥有对数据的所有权。并且可以实现很多额外的功能。

Rust智能指针有什么用,解决了什么问题

它提供了许多强大的抽象来帮助程序员管理内存和并发。其中一些抽象包括智能指针和内部可变性类型,它们可以帮助你更安全、更高效地管理内存,例如Box<T> 用于在堆上分配值。Rc<T> 是一种引用计数类型,可以实现数据的多重所有权。RefCell<T> 提供内部可变性,可用于实现对同一数据的多个可变引用

它们在标准库中定义,可以用来更灵活地管理内存,智能指针的一个特点就是实现了Drop和Deref这两个trait。其中Drop trait中提供了drop方法,在析构时会去调用。Deref trait提供了自动解引用的能力,让我们在使用智能指针的时候不需要再手动解引用了

Rust有哪些常用智能指针

  • Box<T>是最简单的智能指针,它允许你在堆上分配值并在离开作用域时自动释放内存。
  • Rc<T>Arc<T>是引用计数类型,它们允许多个指针指向同一个值。当最后一个指针离开作用域时,值将被释放。Rc<T>不是线程安全的,而Arc<T>是线程安全的。

内部可变性类型允许你在不可变引用的情况下修改内部值。Rust中有几种内部可变性类型,包括Cell<T>RefCell<T>UnsafeCell<T>

  • Cell<T>是一个内部可变性类型,它允许你在不可变引用的情况下修改内部值。Cell<T>只能用于Copy类型,因为它通过复制值来实现内部可变性。
  • RefCell<T>也是一个内部可变性类型,它允许你在不可变引用的情况下修改内部值。与Cell<T>不同,RefCell<T>可以用于非Copy类型。它通过借用检查来确保运行时的安全性。
  • UnsafeCell<T>是一个底层的内部可变性类型,它允许你在不可变引用的情况下修改内部值。与Cell<T>RefCell<T>不同,UnsafeCell<T>不提供任何运行时检查来确保安全性。因此,使用UnsafeCell<T>可能会导致未定义行为。

此外,Rust还提供了一种弱引用类型Weak<T>,它可以与Rc<T>Arc<T>一起使用来创建循环引用。Weak<T>不会增加引用计数,因此它不会阻止值被释放。

Box<T>

Box<T>是最简单的智能指针,它允许你在堆上分配值并在离开作用域时自动释放内存。

Box<T>通常用于以下情况:

  • 当你有一个类型,但不确定它的大小时,可以使用Box<T>来在堆上分配内存。例如,递归类型通常需要使用Box<T>来分配内存。
  • 当你有一个大型数据结构并希望在栈上分配内存时,可以使用Box<T>来在堆上分配内存。这样可以避免栈溢出的问题。
  • 当你希望拥有一个值并只关心它的类型而不是所占用的内存时,可以使用Box<T>。例如,当你需要将一个闭包传递给函数时,可以使用Box<T>来存储闭包。

总之,当你需要在堆上分配内存并管理其生命周期时,可以考虑使用Box<T>

下面是一个简单的例子:

  1. fn main() {
  2. let b = Box::new(5);
  3. println!("b = {}", b);
  4. }
  5. 复制代码

这里定义了变量 b,其值是一个指向被分配在堆上的值 5 的 Box。这个程序会打印出 b = 5;在这个例子 中,我们可以像数据是储存在栈上的那样访问 box 中的数据。正如任何拥有数据所有权的值那样,当像 b 这样的 box 在 main 的末尾离开作用域时,它将被释放。这个释放过程作用于 box 本身(位于栈上) 和它所指向的数据(位于堆上)。

但是Box<T>无法同时在多个地方对同一个值进行引用

  1. enum List {
  2. Cons(i32, Box),
  3. Nil,
  4. }
  5. use crate::List::{Cons, Nil};
  6. fn main() {
  7. let a = Cons(5, Box::new(Cons(10, Box::new(Nil))));
  8. let b = Cons(3, Box::new(a));
  9. let c = Cons(4, Box::new(a));
  10. }
  11. 复制代码

编译会得出如下错误: error[E0382]: use of moved value: a,因为b和c无法同时拥有a的所有权,这个时候我们要用Rc<T>

Rc<T> Reference Counted 引用计数

Rc<T>是一个引用计数类型,它允许多个指针指向同一个值。当最后一个指针离开作用域时,值将被释放。Rc<T>不是线程安全的,因此不能在多线程环境中使用。

Rc<T>通常用于以下情况:

  • 当你希望在多个地方共享数据时,可以使用Rc<T>。解决了使用Box<T>共享数据时出现编译错误
  • 当你希望创建一个循环引用时,可以使用Rc<T>Weak<T>来实现。

下面是一个简单的例子,演示如何使用Rc<T>来共享数据:

  1. use std::rc::Rc;
  2. fn main() {
  3. let data = Rc::new(vec![1, 2, 3]);
  4. let data1 = data.clone();
  5. let data2 = data.clone();
  6. println!("data: {:?}", data);
  7. println!("data1: {:?}", data1);
  8. println!("data2: {:?}", data2);
  9. }
  10. 复制代码

这个例子中,我们使用Rc::new来创建一个新的Rc<T>实例。然后,我们使用clone方法来创建两个新的指针,它们都指向同一个值。由于Rc<T>实现了引用计数,所以当最后一个指针离开作用域时,值将被释放。

但是Rc<T>在多线程中容易引发线程安全问题,为了解决这个问题,又有了Arc<T>

Arc<T> Atomically Reference Counted 原子引用计数

Arc<T>是一个线程安全的引用计数类型,它允许多个指针在多个线程之间共享同一个值。当最后一个指针离开作用域时,值将被释放。

Arc<T>通常用于以下情况:

  • 当你希望在多个线程之间共享数据时,可以使用Arc<T>,是Rc<T>的多线程版本。
  • 当你希望在线程之间传递数据时,可以使用Arc<T>来实现。

下面是一个简单的例子,演示如何使用Arc<T>来在线程之间共享数据:

  1. use std::sync::Arc;
  2. use std::thread;
  3. fn main() {
  4. let data = Arc::new(vec![1, 2, 3]);
  5. let data1 = data.clone();
  6. let data2 = data.clone();
  7. let handle1 = thread::spawn(move || {
  8. println!("data1: {:?}", data1);
  9. });
  10. let handle2 = thread::spawn(move || {
  11. println!("data2: {:?}", data2);
  12. });
  13. handle1.join().unwrap();
  14. handle2.join().unwrap();
  15. }
  16. 复制代码

这个例子中,我们使用Arc::new来创建一个新的Arc<T>实例。然后,我们使用clone方法来创建两个新的指针,它们都指向同一个值。接着,我们在线程中使用这些指针来访问共享数据。由于Arc<T>实现了线程安全的引用计数,所以当最后一个指针离开作用域时,值将被释放。

Weak<T> 弱引用类型

Weak<T>是一个弱引用类型,它可以与Rc<T>Arc<T>一起使用来创建循环引用。Weak<T>不会增加引用计数,因此它不会阻止值被释放。

当你希望创建一个循环引用时,可以使用Rc<T>Arc<T>Weak<T>来实现。

Weak<T>通常用于以下情况:

  • 当你希望观察一个值而不拥有它时,可以使用Weak<T>来实现。由于Weak<T>不会增加引用计数,所以它不会影响值的生命周期。

下面是一个简单的例子,演示如何使用Rc<T>Weak<T>来创建一个循环引用:

  1. use std::rc::{Rc, Weak};
  2. struct Node {
  3. value: i32,
  4. next: Option<Rc<Node>>,
  5. prev: Option<Weak<Node>>,
  6. }
  7. fn main() {
  8. let first = Rc::new(Node { value: 1, next: None, prev: None });
  9. let second = Rc::new(Node { value: 2, next: None, prev: Some(Rc::downgrade(&first)) });
  10. first.next = Some(second.clone());
  11. }
  12. 复制代码

这个例子中,我们定义了一个Node结构体,它包含一个值、一个指向下一个节点的指针和一个指向前一个节点的弱引用。然后,我们创建了两个节点firstsecond,并使用Rc::downgrade方法来创建一个弱引用。最后,我们将两个节点连接起来,形成一个循环引用。

需要注意的是,由于Weak<T>不会增加引用计数,所以它不会阻止值被释放。当你需要访问弱引用指向的值时,可以使用upgrade方法来获取一个临时的强引用。如果值已经被释放,则upgrade方法会返回None

UnsafeCell<T>

UnsafeCell<T>是一个底层的内部可变性类型,它允许你在不可变引用的情况下修改内部值。与Cell<T>RefCell<T>不同,UnsafeCell<T>不提供任何运行时检查来确保安全性。因此,使用UnsafeCell<T>可能会导致未定义行为。

由于UnsafeCell<T>是一个底层类型,它通常不直接用于应用程序代码。相反,它被用作其他内部可变性类型(如Cell<T>RefCell<T>)的基础。

下面是一个简单的例子,演示如何使用UnsafeCell<T>来修改内部值:

  1. use std::cell::UnsafeCell;
  2. fn main() {
  3. let x = UnsafeCell::new(1);
  4. let y = &x;
  5. let z = &x;
  6. unsafe {
  7. *x.get() = 2;
  8. *y.get() = 3;
  9. *z.get() = 4;
  10. }
  11. println!("x: {}", unsafe { *x.get() });
  12. }
  13. 复制代码

这个例子中,我们使用UnsafeCell::new来创建一个新的UnsafeCell<T>实例。然后,我们创建了两个不可变引用yz,它们都指向同一个值。接着,在一个unsafe块中,我们使用get方法来获取一个裸指针,并使用它来修改内部值。由于UnsafeCell<T>实现了内部可变性,所以我们可以在不可变引用的情况下修改内部值。

需要注意的是,由于UnsafeCell<T>不提供任何运行时检查来确保安全性,所以使用它可能会导致未定义行为。因此,在大多数情况下,你应该使用其他内部可变性类型(如Cell<T>RefCell<T>),而不是直接使用UnsafeCell<T>

Cell<T>

Cell<T>是一个内部可变性类型,它允许你在不可变引用的情况下修改内部值。Cell<T>只能用于Copy类型,因为它通过复制值来实现内部可变性。

Cell<T>通常用于以下情况:

  • 当你需要在不可变引用的情况下修改内部值时,可以使用Cell<T>来实现内部可变性。
  • 当你需要在结构体中包含一个可变字段时,可以使用Cell<T>来实现。 下面是一个简单的例子,演示如何使用Cell<T>来修改内部值:
  1. use std::cell::Cell;
  2. fn main() {
  3. let x = Cell::new(1);
  4. let y = &x;
  5. let z = &x;
  6. x.set(2);
  7. y.set(3);
  8. z.set(4);
  9. println!("x: {}", x.get());
  10. }
  11. 复制代码

这个例子中,我们使用Cell::new来创建一个新的Cell<T>实例。然后,我们创建了两个不可变引用yz,它们都指向同一个值。接着,我们使用set方法来修改内部值。由于Cell<T>实现了内部可变性,所以我们可以在不可变引用的情况下修改内部值。

需要注意的是,由于Cell<T>通过复制值来实现内部可变性,所以它只能用于Copy类型。如果你需要在不可变引用的情况下修改非Copy类型的值,可以考虑使用RefCell<T>

RefCell<T>

RefCell<T>是一个内部可变性类型,它允许你在不可变引用的情况下修改内部值。与Cell<T>不同,RefCell<T>可以用于非Copy类型。

RefCell<T>通过借用检查来确保运行时的安全性。当你尝试获取一个可变引用时,RefCell<T>会检查是否已经有其他可变引用或不可变引用。如果有,则会发生运行时错误。

RefCell<T>通常用于以下情况:

  • 当你需要在不可变引用的情况下修改内部值时,可以使用RefCell<T>来实现内部可变性。

  • 当你需要在结构体中包含一个可变字段时,可以使用RefCell<T>来实现。

下面是一个简单的例子,演示如何使用RefCell<T>来修改内部值:

  1. use std::cell::RefCell;
  2. fn main() {
  3. let x = RefCell::new(vec![1, 2, 3]);
  4. let y = &x;
  5. let z = &x;
  6. x.borrow_mut().push(4);
  7. y.borrow_mut().push(5);
  8. z.borrow_mut().push(6);
  9. println!("x: {:?}", x.borrow());
  10. }
  11. 复制代码

这个例子中,我们使用RefCell::new来创建一个新的RefCell<T>实例。然后,我们创建了两个不可变引用yz,它们都指向同一个值。接着,我们使用borrow_mut方法来获取一个可变引用,并使用它来修改内部值。由于RefCell<T>实现了内部可变性,所以我们可以在不可变引用的情况下修改内部值。

需要注意的是,由于RefCell<T>通过借用检查来确保运行时的安全性,所以当你尝试获取一个可变引用时,如果已经有其他可变引用或不可变引用,则会发生运行时错误。from刘金,转载请注明原文链接。感谢!

 

Rust中的智能指针:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak<T>的更多相关文章

  1. OSG中的智能指针

    在OpenSceneGraph中,智能指针(Smart pointer)的概念指的是一种类的模板,它针对某一特定类型的对象(即Referenced类及其派生类)构建,提供了自己的管理模式,以避免因为用 ...

  2. RPCZ中的智能指针单例

    RPCZ中的智能指针单例 (金庆的专栏) 智能指针单例应用于 RPCZ 库以实现库的自动初始化与自动清理. RPCZ: RPC implementation for Protocol Buffers ...

  3. Boost中的智能指针(转)

    这篇文章主要介绍 boost中的智能指针的使用.(转自:http://www.cnblogs.com/sld666666/archive/2010/12/16/1908265.html) 内存管理是一 ...

  4. C++中的智能指针、轻量级指针、强弱指针学习笔记

    一.智能指针学习总结 1.一个非const引用无法指向一个临时变量,但是const引用是可以的! 2.C++中的delete和C中的free()类似,delete NULL不会报"doubl ...

  5. ATL和vc++中的智能指针(分别是CComPtr和_com_ptr_t)

    一.智能指针的概念 智能指针是一个类,不是指针,智能指针在所包含的指针不再被使用时候会自动释放该所包含指针所占用的系统资源,而不用手动释放. 原理:智能指针封装了包含指针的AddRef()函数和Rel ...

  6. 标准库中的智能指针shared_ptr

    智能指针的出现是为了能够更加方便的解决动态内存的管理问题.注:曾经记得有本书上说可以通过vector来实现动态分配的内存的自动管理,但是经过试验,在gcc4.8.5下是不行的.这个是容易理解的,vec ...

  7. 智能指针类模板(中)——Qt中的智能指针

    Qt中的智能指针-QPointer .当其指向的对象被销毁时,它会被自动置空 .析构时不会自动销毁所指向的对象-QSharedPointer .引用计数型智能指针 .可以被自由的拷贝和赋值 .当引用计 ...

  8. 智能指针类模板(上)——STL中的智能指针

    智能指针类模板智能指针本质上就是一个对象,它可以像原生指针那样来使用. 智能指针的意义-现代C++开发库中最重要的类模板之一-C++中自动内存管理的主要手段-能够在很大程度上避开内存相关的问题 1.内 ...

  9. C++中的智能指针类模板

    1,智能指针本质上是一个对象,这个对象可以像原生的指针一样使用,因为智能指 针相关的类通过重载的技术将指针相关的操作符都进行了重载,所以智能指针对象可以像原生指针一样操作,今天学习智能指针类模板,通过 ...

  10. C++ 中的智能指针-基础

    简介 在现代 C++ 编程中,标准库包含了智能指针(Smart pointers). 智能指针用来确保程序不会出现内存和资源的泄漏,并且是"异常安全"(exception-safe ...

随机推荐

  1. 05.常用 API 第二部分

    一.Object 类 是类层次结构的根 (父) 类. String  toString () 返回该对象的字符串表示,其实该字符串内容就是对象的类型 + @ + 内存地址值. 由于 toString ...

  2. HarmonyOS_Text_Image

    Text组件 ohos:属性 id="$+id:text_helloworld" #在程序中控制,需要id="$+id:name",转回MainAbilityS ...

  3. python,数据类型和变量,数据类型和变量,集合,字符串拼接

    可不可变: 可变:列表,字典 不可变:字符串,数字,元祖 访问顺序: 直接访问:数字 顺序访问:字符串,列表,元祖 映射:字典 存放元素个数 容器类型:列表,元祖,字典 原子:数字,字符串 集合 1. ...

  4. 各种系统名词解释:MIS 、ERP、CRM、OA

    MIS :信息系统.针对企业使用的软件,都可以叫做MIS系统. (管理信息系统--Management Information System)系统 ,是一个由人.计算机及其他外围设备等组成的能进行信息 ...

  5. k8s集群跨namespace访问服务

    场景:自己有一个java应用部署在test命名空间下,但是一直无法访问到middleware命名空间下的mysql服务 查找资料发现下面所说的问题 我们通常会把mysql,redis,rabbitmq ...

  6. TP5中redirect实现重定向及带参数跳转

    1.控制器 重定向url传参 try{ $result = Db::name('wupin')->insert($ist); if($result){ $this->redirect(ur ...

  7. 学习记录--C++组合+依赖+依赖倒置

    组合关系:表示类之间的关系是整体与部分的关系.即has a / contains a的关系 在面向对象程序设计中,将一个复杂对象分解为简单对象的组合. 在代码中,体现为将一个或多个类的对象作为另一个类 ...

  8. Python学习笔记--数据输出

    数据输出 输出为Python对象 collect算子 具体实现: reduce算子 具体实现: take算子 具体实现: count算子 具体实现: 输出到文件中 saveAsTextFile算子 具 ...

  9. linux环境下部署mysql环境

    一.部署步骤 1.将安装包上传到Linux服务器上(目录随意),然后解压缩 2.进入到解压后的目录下,分别执行以下命令安装四个包(严格按照顺序执行) rpm -ivh mysql-community- ...

  10. wsl 中 docker-compose 搭建 kafka 集群出现的外部访问错误

    在 wsl 中用 docker-compose 搭建了一台 zookeeper + 三台 broker 的 kafka 集群,使用的镜像是 bitnami/kafka,在按照镜像文档运行容器后,发现运 ...