rust学习十五.2、智能指针之Box(盒子)指针

打印 上一主题 下一主题

主题 872|帖子 872|积分 2616

Box 原意是盒子,实际生存是作为容器利用。
Rust没有称为Container,而是称为Box,体现了他们一贯的随意性。
不外Box这个词汇倒是可以反应盒子指针的特点:就是一个容器,没有什么特别的。
一、盒子指针定义
  1. #[lang = "owned_box"]
  2. #[fundamental]
  3. #[stable(feature = "rust1", since = "1.0.0")]
  4. #[rustc_insignificant_dtor]
  5. // The declaration of the `Box` struct must be kept in sync with the
  6. // compiler or ICEs will happen.
  7. pub struct Box<
  8.     T: ?Sized,
  9.     #[unstable(feature = "allocator_api", issue = "32838")] A: Allocator = Global,
  10. >(Unique<T>, A);
复制代码
其定义倒也满足所谓智能指针要素要求:有元数据,有额外功能
只不外,这个定义,对于初学者而言,颇有难度,由于熟悉的字母虽多,但是不熟悉的词汇和语法确居多。
借助有关工具,我们一一看看,这些编译器注解和词汇的含义。

  •    #[lang = "owned_box"] --指定一个item(通常是结构体或范例别名)作为Rust语言的内置范例.这个属性告诉编译器,被注解的item是语言内置的一部分,用于实现特定的语言特性
    1. #[fundamental]  标记一个item是Rust语言基础设施的一部分,通常指的是那些对于Rust运行时和编译器操作至关重要的类型或函数
    复制代码
    1. #[stable(feature = "rust1", since = "1.0.0")] -- 标记一个item的稳定性状态
    复制代码
    1. #[rustc_insignificant_dtor] -- 标记一个类型的析构函数(drop实现)对于性能或资源管理来说不重要
    复制代码
  • ?Sized     -- 利用一个不定巨细的泛型范例
  • Alllocator -- 用于处理内存分配的特质
  • Global     -- 全局内存分配器,一个实现了Allocator的结构体
  • Unique    -- 非空数据包装器,也是结构范例
刨除这些编译器注解,那么一个Box重要包含两个成员:数据包装器、全局内存分配器
在标准库boxed.rs中,为Box定义了许多的实现,此处略。
三、盒子指针的应用场景

原书是这么写的:

  • 当有一个在编译时未知巨细的范例,而又想要在需要确切巨细的上下文中利用这个范例值的时候   -- 例如定义递归数据结构
  • 当有大量数据并盼望在确保数据不被拷贝的情况下转移所有权的时候 -- 这有点拗口
       但这个照旧让人迷惑?担心在转移所有权的时候,被复制了一份数据? 又转移又拷贝? 暂时没有示例帮助理解这句话。

  • 当盼望拥有一个值并只关心它的范例是否实现了特定 trait 而不是其具体范例的时候。 这个倒是可以理解,由于Box实现了许多特质
        但是,着实不消box也能实现这个。
个人觉得,第一个是最重要的,由于其它的,都可以通过其它方式做到替代。
 
三、协助定义递归数据结构

学到这里,书籍中已经多次提到:Rust 需要在编译时知道范例占用多少空间
换言之,如果无法知道,那么就必须采用曲折的方式让rustc知道这一点。
这同时也意味着,这是一种取巧,并不是真正知道占用多少,只是在某种层面是知道的。
 
递归数据难点就在于:层次不限,无法知道动态情况下的真正巨细。
rust的做法是一种障眼术:把盒子指针作为递归结构的成员(子孙),那么分析顶层结构的时候,总是认为其它巨细是固定的,由于一个最简单的递归结构通常有两个项目:数据和子孙。
考虑到子孙是盒子指针,其巨细是确定的,故整个递归结构至少在顶层上是可以或许明白其巨细的。
如许说有点绕口,看以下模仿有关书籍编写的例子:
  1. enum RecursiveEnum {
  2.     Data(i32, Box<RecursiveEnum>),
  3.     Nil,
  4. }
  5. struct RecursiveStruct {
  6.     data: i32,
  7.     next: Option<Box<RecursiveStruct>>,
  8. }
  9. use RecursiveEnum::{ Data, Nil };
  10. fn main() {
  11.     //这两种无论哪一种都是比较怪异的。在实际编程中基本上不可能这样去初始化数据
  12.     let list = Data(1, Box::new(Data(2, Box::new(Data(3, Box::new(Nil))))));
  13.     let org = RecursiveStruct {
  14.         data: 1,
  15.         next: Some(
  16.             Box::new(RecursiveStruct {
  17.                 data: 2,
  18.                 next: Some(
  19.                     Box::new(RecursiveStruct {
  20.                         data: 3,
  21.                         next: None,
  22.                     })
  23.                 ),
  24.             })
  25.         ),
  26.     };
  27. }
复制代码
 
以上文的RecursiveStruct为例子:
  1. struct RecursiveStruct {
  2.     data: i32,
  3.     next: Option<Box<RecursiveStruct>>,
  4. }
复制代码
 
由于data是确定巨细的,next也是确定的(由于next是基于Box的Option范例)。
说着实,在其它语言中,定义递归结构都很容易,例如java,js等,也不需要特别费脑力。
但rust这里由于其特别的编译原则,书籍的作者不得不大费周章解释为什么。
为什么要这么做,这需要理解一个要点:Rust 需要在编译时知道范例占用多少空间
怎样理解?

  • 记住这是一个规定。如果不理解请服膺这点。没有太多人会想去研究rustc是怎样编写,怎样工作的细节。
  • 简单和确定的可以或许带来安全和性能,这个应该都能理解。
  • rust内存管理,包罗栈内存管理,如果知道巨细,自然好管理
  • 其它暂时还不需要理解的编译器内核
四、 盒子指针试用

定义递归的示例见前文,此处略。
  1.     let cn=String::from("中国");
  2.     let cn_box=Box::new(cn);
  3.     println!("length of {} is {}",cn_box,cn_box.len());
  4.     let pcn=*cn_box;
  5.     println!("length of {} is {}",pcn,pcn.len());
  6.     //let pcn2=*cn_box;   这里会报错,因为 *Box 返回的是Box值的所有权,而不是引用
复制代码
以上例子,有许多非Box所特有的概念才能解释的,此处略,只能提一下:Deref,Drop, 引用隐式转换
尤其是Deref,Drop两个特质,有了它们,Box才有更大的作用。
五、小结


  • 盒子指针是比较简单的一种智能指针
  • 它让人印象深刻的地方在于:可以辅助定义递归数据
  • 它有多个应用场景,但只有递归让我印象深刻
  • 它的功能绝对不限于辅助定义递归,否则boxes.rs不需要写那么多
  • 通过理解 Box的定义,可以或许大要明白更加复杂的智能指针是什么样的
  • 光通过本文的内容,还无法完全理解盒子指针。所以后面还会有文章阐述Deref,Drop等特质

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

美丽的神话

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表