返回介绍

7.7.2 智能

发布于 2024-10-13 11:25:30 字数 4712 浏览 0 评论 0 收藏 0

智能指针(smart pointer)是一类数据结构,其行为类似指针,但拥有额外功能。与引用的差别在于,引用 借用 (borrow)目标,而智能指针则 拥有 (own)目标。

智能指针通常实现 DerefDrop 特征。

  • Deref : 重载解引用运算符,使其操作与引用一致。
  • Drop : 负责在离开作用域时清理资源。

类型:

  • Box<T> : 在堆上分配值。
  • Rc<T> : 引用计数,可以有多个所有者。
  • RefCell<T> : 以不可变引用修改值。

Box

在堆(heap)上为值分配内存,栈上只是一个指向该内存的指针。

  • 单一所有者。
  • 运行时才知所需内存大小。
  • 转移所有权时,不想复制数据。
   stack        heap
  +=====+       +=======+
  | ptr | --------> | value |  Box<T>
  +=====+       +=======+

使用方式和引用一致,没什么特殊功能。超出作用域自动释放。

fn main() {
  
  // 在堆上分配 sizeof(i32) 大小内存,并存储值 5。
  let mut p = Box::new(5);
  
  *p += 1;
  let x: i32 = *p;
  
  assert_eq!(x, 6);
}
fn main() {
  // 以数组实现类 calloc 功能。
  let mut p = Box::new([0i64; 10]);

  p[1] = 10;
  p[2] = 20;

  println!("{:?} {:?}", p[2], p);
}

Rc

引用计数(reference counting)启用多所有权。当引用数归零时,进行清理。

  • 堆分配内存,共享所有权。
  • 单线程,使用非原子计数。
  • 不可变引用,无法修改值。
  • 降级获取没有所有权的弱指针(weak pointer)。
  • 存在循环引用无法释放的问题(用弱指针解决)。
   stack        heap
  +=====+       +============+
  | ptr | --------> | strong_cnt |  Rc<T>
  +=====+       +------------+
          | weak_cnt   |
          +------------+
          | value    |
          +============+
          
use std::rc::Rc;

fn main() {
  let rc = Rc::new(5);
  assert_eq!(Rc::strong_count(&rc), 1);

  {
    // 增加引用,计数 +1。
    let rc2 = rc.clone();
    assert_eq!(Rc::strong_count(&rc), 2);
    
    // 解引用。
    assert_eq!(*rc2, 5);
    
  }   // 超出作用域,计数 -1。

  assert_eq!(Rc::strong_count(&rc), 1);
}
use std::rc::{Rc, Weak};

fn main() {
  let wr: Weak<i32>;

  {
    let rc = Rc::new(5);

    // 降级,生成弱引用。
    wr = Rc::downgrade(&rc);

    // 不影响强引用计数。
    assert_eq!(Rc::strong_count(&rc), 1);
    assert_eq!(Rc::weak_count(&rc), 1);

    // 弱引用不能保证目标值存活,所以不能直接解引用。
    // 升级成强引用再操作。如已释放,返回 None。
    
    if let Some(rc2) = wr.upgrade() {
      assert_eq!(*rc2, 5);
    } else {
      panic!("upgrade: None");
    }
    
  } // Rc drop!!!

  assert_eq!(wr.upgrade(), None);
}

自动解引用,可直接调用值方法。

use std::rc::Rc;

struct User {
  age: u8
}

impl User {
  fn test(&self) {
    println!("{}", self.age);
  }
}

fn main() {
  let rc = Rc::new(User{ age: 10 });
  rc.test();
}

另有原子版本的 Arc,参考后续《并发:同步》章节。

RefCell

内部可变性 (Interior mutability)绕过借用规则,允许通过不可变引用修改内部值。

  • 单一所有权,运行期借用规则检查。
  • 运行期行为,出错 panic!
  • 单线程。

编译器静态规则检查过于保守,某些设计需要运行期操作。

  +============+
  | borrow_cnt |  RefCell<T>
  +------------+  borrow_mut: count = -1
  | value    |
  +============+
          

获取可变和不可变引用。

方法 borrowborrow_mut 返回 Ref<T>RefMut<T>
它们实现了 Deref ,但 RefCell 没有。

use std::cell::RefCell;

fn main() {
  let c = RefCell::new(5);

  {
    let r1 = c.borrow();  // &T
    let r2 = c.borrow();  // &T

    assert_eq!(*r1, 5);
    assert_eq!(*r1, *r2);
  }

  {
    let mut r = c.borrow_mut(); // &mut T
    *r = 10;
  }

  assert_eq!(c.into_inner(), 10);
}
fn main() {
  let c = RefCell::new(5);

  let mut r1 = c.borrow_mut(); // &mut T

  // 同一时刻,不能有多个可变引用。
  let mut r2 = c.borrow_mut(); // &mut T
           ~~~~~~~~~~ already borrowed: BorrowMutError

  *r1 += 1;
  *r2 += 1;
}

改造 Rc ,使其可变。

use std::rc::Rc;
use std::cell::RefCell;

fn main() {
  let rc = Rc::new(RefCell::new(5));
  let rc2 = rc.clone();

  {
    let mut r = rc.borrow_mut();
    *r = 100;
  }

  assert_eq!(*rc2.borrow(), 100);
}

Cell

便捷版本,开辟一块始终可变内存区域。

  • RefCell 提供引用, Cell 提供值。
  • RefCell 动态借用检查规则, Cell 是可变内存位置。
use std::cell::Cell;

struct Data {
  x: i64,
  y: Cell<i64>,
}

fn main() {
  let d = Data{ x: 1, y: Cell::new(2) };
  
  // d.x = 10;
  // ^^^^^^^^ cannot assign

  d.y.set(200);
  assert_eq!(d.y.get(), 200);   // copy
  assert_eq!(d.y.get(), 200);   // copy

  assert_eq!(d.y.take(), 200);  // move
  assert_eq!(d.y.take(), 0);
}

Choosing your Guarantees

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文