Rust学习笔记Day17 智能指针之Box<T>

2023-02-23 16:59:28 浏览数 (1)

经过这一段时间的学习,基础知识里,我们还剩数据结构没有学习,而数据结构里最难的就是智能指针

我们先回顾一下指针:它的值是一个内存地址,要想访问它指向的这个内存地址,需要解引用。理论上可以解引用到任意数据类型;引用是一个特殊的指针,它只能解引用到它引用的数据类型

智能指针

智能指针除了指向数据的指针外,还有源数据以提供额外的处理能力。

智能指针与胖指针的区别:智能指针一定是一个胖指针,但胖指针不一定是一个智能指针。

我们来看一个例子:

String(智能指针) 对堆上的值有所有权,而 &str(胖指针) 是没有所有权的,这是 Rust 中智能指针和普通胖指针的区别。

代码语言:javascript复制
pub struct String {
    vec: Vec<u8>,
}

从String的定义来看明明就是个结构体,怎么就高攀上了智能指针了呢?这是因为String 实现了 我们昨天刚学的 Deref 和 DerefMut 这2个trait,这使得它在解引用的时候,会得到 &str。

代码语言:javascript复制
impl ops::Deref for String {
    type Target = str;

    fn deref(&self) -> &str {
        unsafe { str::from_utf8_unchecked(&self.vec) }
    }
}

impl ops::DerefMut for String {
    fn deref_mut(&mut self) -> &mut str {
        unsafe { str::from_utf8_unchecked_mut(&mut *self.vec) }
    }
}

由于在堆上分配了数据,String 还需要为其分配的资源做相应的回收。而 String 内部使用了 Vec,所以它可以依赖 Vec的能力来释放堆内存。

现在我们发现,在Rust中,但凡是要回收资源,并实现了Deref/DerefMut/Drop这3个trait的数据结构,都是智能指针

这么说的话,我们发现以下这些都是智能指针

  • 用于在堆上分配内存的Box<T> 和 Vec <T>、
  • 用于引用计数的 Rc<T> 和 Arc <T>
  • 还有很多其他的数据结构 PathBuf、Cow <'a B>、MutexGuard <T> 等。

Box<T>

它是 Rust 中最基本的在堆上分配内存的方式,绝大多数其它包含堆内存分配的数据类型,内部都是通过 Box完成的,比如 Vec<T>。

我们先来复习一下C是怎么分配堆内存的。C提供了malloc/calloc/realloc/free 来处理内存分配,但是最后为了安全释放内存,给我们码农造成比较大的心智负担。

C 发现这个问题,于是搞了一个智能指针unique,可以在指针退出作用域的时候释放堆内存,这样保证了堆内存的单一所有权。这就是Box<T>的前身。

我们看到在Box<T>的定义里就有Unique表示借鉴的C

堆上分配内存的 Box其实有一个缺省的泛型参数 A,就需要满足 Allocator trait,这其实是指定一种内存分配器,并且默认是 Global,当然也可以替换成自己的内存分配器。

Allocator trait 提供很多方法:

  • allocate 是主要方法,用于分配内存,对应 C 的 malloc/calloc;
  • deallocate,用于释放内存,对应 C 的 free;
  • 还有 grow / shrink,用来扩大或缩小堆上已分配的内存,对应 C 的 realloc。

再来看下Box的Drop实现:

代码语言:javascript复制
#[stable(feature = "rust1", since = "1.0.0")]
unsafe impl<#[may_dangle] T: ?Sized, A: Allocator> Drop for Box<T, A> {
    fn drop(&mut self) {
        // FIXME: Do nothing, drop is currently performed by compiler.
    }
}

可以看到这是个空的实现。像我们平时开发一样,先确定接口,然后各自去自己的服务里实现自己的逻辑。这是不是就是我们一直说的面向接口编程的思想?

0 人点赞