21.智能指针(上)

一、概念

  • 在Rust中,引用是只是借用数据的指针,智能指针拥有它们所指向的数据的所有权;
  • 智能指针通常使用结构体实现;
  • 智能指针实现了Deref trait,值可以被当作引用对待;
  • 智能指针实现了Drop trait,当值离开作用域时,其所指向的堆数据也去被清除;
  • 常用的智能指针见下表
指针功能说明
Box<T>用于在堆上分配值,允许在编译时执行不可变或可变借用检查
Rc<T>一个引用计数类型,相同数据可以有多个所有者,仅允许在编译时执行不可变借用检查
RefCell<T>允许在运行时执行不可变或可变借用检查;可以在即使RefCell<T> 自身是不可变的情况下修改其内部的值
Ref<T>RefMut<T>通过RefCell<T> 访问
  • 内部可变性模式:在不可变值内部改变值;

二、Box<T>

2.1 概念与应用场景

  • box是最简单最直接的智能指针,其类型是box<T>
  • box主要应用于以下场景:
    • 编译时未知大小的类型,但使用时却需要知道它的确切大小;
    • 大量数据且希望在确保数据不被拷贝的情况下转移所有权;
    • 只关心值的类型是否实现了特定 trait;

2.2 简单应用

fn main(){
    let b = Box::new(5);
    println!("b = {}", b);
}
  • 变量b指向了分配在上的值为5的Box;
  • b拥有这块内存的所有权,离开作用域后堆内存被自动释放;

2.3 递归类型的创建

  • Rust需要在编译时知道类型占用的空间大小;
  • box的已知大小,让其可以在循环类型定义中插入box,就可以创建递归类型;
enum List{
    Cons(i32, Box<List>),
    Nil,
}

use crate::List::{Cons, Nil};
fn main() {
    let list = Cons(1,Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil))))));
}
  • Cons成员将需要一个i32类型的空间大小以及box指针数据的空间;
  • Nil成员不存储值,因此它比Cons成员需要更少的空间;
  • 看起来像这样

在这里插入图片描述

  • 如果不用Box定义递归,写成下面这样

enum List{
    Cons(i32, List),
    Nil,
}

use crate::List::{Cons, Nil};
fn main() {
    let list = Cons(1, Cons(2, Cons(3, Nil)));
}
  • 则编译报错,表明类型占用的空间无限大

在这里插入图片描述

  • 其空间排布类型于

在这里插入图片描述

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

  • 实现Deref trait可以让使用者重载解引用运算符(dereference operator) *
  • 这种方式实现Deref trait的智能指针可以被当作常规引用来对待,可以编写操作引用的代码并用于智能指针;

3.1 常规引用

  • 常规引用是一种指针类型;
fn main() {
    let x = 5;
    let y = &x;

    assert_eq!(5, x);
    assert_eq!(5, *y);
}
  • y等于x的引用,使用*y访问x的值;

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

    let x = 5;
    let y = Box::new(x);

    assert_eq!(5, x);
    assert_eq!(5, *y);
  • 代码可正常运行不报错;

3.3 自定义智能指针

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);
}
  • MyBox<T> 被定义为包含一个元素的元组结构体;
  • new函数获取一个T类型的参数并返回一个存入传入值的实例;
  • 为MyBox实现Deref trait才能启动*运算符的解引用功能;
  • impl<T> Deref for MyBox<T>中的type Target = T 定义了此trait的关联类型;
  • deref方法返回了一个值的引用,如果直接返回值,则值的选择权将被移出self;
  • 当使用*y时,底层运行了代码*(y.deref())

3.4 函数和方法的隐式解引用强制转换

  • 解引用强制转换只能工作在实现了Dereftrait 的类型上;
  • 解引用强制转换是将一种类型隐式转换为另外一种类型的引用;
  • 前一种类型实现了Dereftrait,并且其关联类型是后一种类型;

例如,解引用强制转换可以将 &String 转换为 &str,因为类型 String 实现了 Deref trait 并且其关联类型是 str;

#[stable(feature = "rust1", since = "1.0.0")]
impl ops::Deref for String {
    type Target = str;

    #[inline]
    fn deref(&self) -> &str {
        unsafe { str::from_utf8_unchecked(&self.vec) }
    }
}
  • 将特定类型的值的引用传递给函数且与函数定义的参数类型不匹配时,会发生解引用强制转换
  • 此时有一系列的deref方法被调用,将我们提供的参数类型转换成函数或方法需要的参数类型;
  • 解引用强制转换功能可以让开发者编写函数和方法调用时无需增加过多显式使用&和*引用和解引用。
  • 解引用强制转换功能也使得开发者可以编写更多同时作用于引用或智能指针的代码;
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 hello(name: &str){
    println!("Hello, {}", name);
}

fn main() {
    let m = MyBox::new(String::from("Rust"));
    hello(&m);
}
  • main函数中的m为MyBox<String>值的引用;
  • MyBox<T>上实现了Dereftrait,Rust可以通过deref调用将&MyBox<String>变为&String
  • 再次调用deref将&String 变为 &str;
  • 如果没有实现解引用强制转换,为了使用&MyBox<String>类型的值调用hello函数,应该这样写
fn main() {
    let m = MyBox::new(String::from("Rust"));
    hello(&(*m)[..]);
}
  • (*m) 将 MyBox<String> 解引用为 String;
  • &和[…] 获取了整个 String 的字符串 slice 来匹配 hello 函数的参数;
  • 没有解引用强制转换所有这些符号混在一起将更难以读写和理解;
  • Rust的解引用强制转换发生在编译,因此在运行时没有损耗!

3.5 解引用强制转换与可变性交互

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

四、使用Drop Trait清理代码

4.1 自动运行

  • 通过实现Droptrait指定变量离开作用域时被执行的代码;
  • 可以理解为析构函数;
struct CustomSmartPointer {
    data: String,
}

impl Drop for CustomSmartPointer {
    fn drop(&mut self) {
        println!("Dropping CustomSmartPointer with data `{}`!", self.data);
    }
}

fn main() {
    {
        let c = CustomSmartPointer { data: String::from("stuff c") };
    }
    let d = CustomSmartPointer { data: String::from("stuff d") };
    let e = CustomSmartPointer { data: String::from("stuff e") };
    println!("CustomSmartPointers created.");
}
  • main函数中离开最内层的大括号后,变量c首先离开作用域,自动调用drop方法;
  • 然后打印CustomSmartPointers created.
  • 变量d、e最后离开作用域,再自动调用对应的drop方法;
  • d、e的输出结果显示,以先进后出的方式调用drop方法;

在这里插入图片描述

4.2 手动丢弃

  • 不能显式的调用drop方法;
  • 如果要在作用域结构之前强制释放变量,使用drop(x)实现;
fn main() {
    {
        let c = CustomSmartPointer { data: String::from("stuff c") };
    }
    let d = CustomSmartPointer { data: String::from("stuff d") };
    drop(d);
    let e = CustomSmartPointer { data: String::from("stuff e") };
    println!("CustomSmartPointers created.");
}

运行代码,可以发现d被提前析构;
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

贱贱的剑

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值