Rust进阶(8)-智能指针

智能指针:Box、Rc、Ref

1. 概述

  1. 指针是一个包含内存地址的变量,这个地址指向一些其它的数据
    1. 智能指针是一类数据结构,它们表现类似于指针,但是也拥有额外的元数据,最明显的,它们拥有一个引用计数。引用计数记录智能指针总共有多少个所有者,并且当没有任何所有者时清除数据。
    2. 普通引用和智能指针的一个额外区别是:引用只是借用数据的指针,而智能指针则是拥有它们指向的数据
  2. 智能指针通常使用结构体实现。智能指针区别于常规结构体的显著特征在于其实现了Deref和Drop trait
    1. Defef trait允许智能指针结构体实例表现的像引用一样,这样就可以编写即 用于引用,又用于智能指针的代码
    2. Drop trait允许我们自定义当智能指针离开作用域时执行的代码
  3. 几个标准库中的智能指针
    1. Box,用于在堆上分配
    2. Rc,一个引用计数类型,其数据可以有多个所有者
    3. Ref和RefMut,通过RefCell访问,一个在运行时而不是在编译时执行借用规则的类型

2. Box

  1. 最简单最直接的智能指针,其类型为Box。box允许将值放在堆而不是栈上,留着栈上的则是指向堆数据的指针。除了数据被存储在堆上外,box没有任何性能损失
  2. box适用于如下场景:
    1. 当有一个在编译时未知大小的类型,而又需要在确切大小的上下文使用这个类型值的时候;例如:在一个list环境下,存放数据,但是每个元素的大小在编译时又不确定
    2. 当有大量数据(数据非常大,拷贝耗性能)并希望在确保数据不被拷贝的情况下转移所有权的时候;
    3. 当希望拥有一个值并只关心它的类型是否实现了特定trait而不是其具体类型时
  3. 基本使用:
1
2
3
4
fn main() {
let b = Box::new(5); //b存储在栈上,5存储在堆上,b指向5所在的内存
println!("b = {}",b)
}

2.1 使用Box

  1. 当有一个在编译时未知大小的类型,而又需要在确切大小的上下文使用这个类型值的时候;例如:在一个list环境下,存放数据,但是每个元素的大小在编译时又不确定,如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
enum List {
Cons(i32, Box<List>),
Nil,
}

fn main() {
use List::Cons;
use List::Nil;

let list = Cons(
1, Box::new(Cons(
2, Box::new(Cons(
3, Box::new(Nil))))));
}

2.2 解引用

解引用是返回内存地址中对应的对象.
也就是说,解引用就是*标记的

实现Defef trait允许我们重载解引用运算符:

1
2
3
4
5
fn main() {
let a: A = A::new(); //前提:a类型必须实现Defef trait
let b = &a;
let c = *b; //解引用
}

使用box解引用

1
2
3
4
5
6
7
8
9
10
fn main() {
let x = 5;
let y = &x;
assert_eq!(5,x);
assert_eq!(5,*y); //解引用

//使用box解引用
let z = Box::new(x);
assert_eq!(5,*z);
}

2.3 自定义实现MyBox

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
use std::ops::Deref;
struct MyBox<T>(T);

impl<T> MyBox<T> {
fn new(x: T) -> MyBox<T> {
MyBox(x)
}
}

impl<T> Deref for MyBox<T>{
type Target = T;
fn deref(&self) -> &T{
&self.0
}
}

fn main() {
let x = 5;
let y = MyBox::new(x);
assert_eq!(5, x);
assert_eq!(5, *y);
}

2.4 解引用多态

解引用多态与可变性交互:

  1. 当T:Deref<Target=U>时,从&T到&U,下方例子为这第一种情况
  2. 当T:DerefMut<Target=U>时,从&mut T 到 &mut U,即可变之间均可解
  3. 当T:Deref<Target=U>时,从&mut T 到 &U,可变到不可变
    1. 从这里可以看出,不能够从不可变到可变
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
use std::ops::Deref;

struct MyBox<T>(T);

impl<T> MyBox<T> {
fn new(x: T) -> MyBox<T> {
MyBox(x)
}
}

impl<T> Deref for MyBox<T>{
type Target = T;
fn deref(&self) -> &T{
&self.0
}
}

fn main() {
let m = MyBox::new(String::from("Rust"));
hello(&m); //将MyBox变为&String,再将String的解引用,变为字符串slice。
}

fn hello(name: &str){
println!("hello, {}", name);
}

3. 引用计数Rc智能指针

当一个数据需要被多个变量共享时,使用Rc指针,该指针原理其实就是计数,具体解释看3.3小节说明

3.1 为什么要用Rc指针

原因,如下代码,创建了a,然后move到了b;但c又要使用a,导致异常

1
2
3
4
5
6
7
8
9
10
11
12
enum List {
Cons(i32,Box<List>),
Nil,
}

use crate::List::{Cons, Nil};

fn 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));
}

3.2 上面问题正确处理方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
enum List {
Cons(i32, Rc<List>),
Nil,
}

use crate::List::{Cons, Nil};
use std::rc::Rc;

fn 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));
//也可以这么写:
let b = Cons(3,a.clone());
let c = Cons(4,a.clone());
}

3.3 原理

  1. 使用到了计数,b使用,则a计数加1;c使用,则a计数再加1;若a或b释放,则减1
  2. 通过Rc允许程序的多个部分之间只读的共享数据,因为相同位置的多个可变引用可能会造成数据竞争和不一致

4. RefCell智能指针

  1. 内部可变性:允许在使用不可变引用时改变数据(看下方案例)
  2. 通过RefCell<T>在运行时检查借用规则(通常情况下,是在编译时检查借用规则),RefCell<T>代表其数据的唯一所有权
  3. 类似于Box<T>Rc<T>只能用于单线程场景
  4. 选择Box<T>Rc<T>RefCell<T>的理由:
    1. 均不是线程安全的
    2. Rc<T>允许相同数据有多个所有者;Box<T>RefCell<T>有单一所有者
    3. Box<T>允许在编译时执行不可变或可变借用检查;Rc<T>仅允许在编译时执行不可变借用检查;RefCell<T>允许在运行时执行不可变或可变借用检查
    4. 因为RefCell<T>允许在运行时执行可变借用检查,所以我们可以在即便RefCell<T>自身不可变的情况下修改其内部的值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#[derive(Debug)]
enum List {
Cons(Rc<RefCell<i32>>, Rc<List>),
Nil,
}

use crate::List::{Cons, Nil};
use std::rc::Rc;

use std::cell::RefCell;

fn main() {
let value = Rc::new(RefCell::new(5));
let a = Rc::new(Cons(Rc::clone(&value), Rc::new(Nil)));
let b = Cons(Rc::new(RefCell::new(6)), Rc::clone(&a));
let c = Cons(Rc::new(RefCell::new(7)), Rc::clone(&a));
println!("a before: {:?}", a);
println!("b before: {:?}", b);
println!("c before: {:?}", c);
println!("+++++++++++++++++");
*value.borrow_mut() += 10;
println!("a after: {:?}", a);
println!("b after: {:?}", b);
println!("c after: {:?}", c);
}

总结

本文编辑完毕

参考

[1] Rust 程序设计语言

  • Copyrights © 2017-2023 Jason
  • Visitors: | Views:

谢谢打赏~

微信