Rust中的智能指针
什么是智能指针?
传统的指针,如C++中的裸指针,需要开发者自己申请和释放,如果开发者在使用过程中疏漏了回收,将会造成内存泄漏,在部署
实施时也会有oom的风险,智能指针即是为了解决这类问题而出现的,老生常谈的shared_ptr,unique_ptr,weak_ptr等,都是通过设计使得代码自动管理回收堆上的内存,提高代码的健壮性和方便性。
什么是Rust中的智能指针?
Rust由于其严格的安全性和所有权机制,除unsafe写法外,所有的堆内存都是通过语言特性管理的。其目的和其他语言一致都是为了健壮性和方便使用,与C++的智能指针使用的机制并无二致,都是使用了RAII(即资源获取即初始化),常见的rust智能指针有Box,Rc,Arc,Weak等。(个人理解:因为rust中的智能指针常与引用符号&结合使用,与其叫做智能指针,不如叫智能引用。)
Rust中的智能指针Box
首先看一下C++中的unique_ptr:
1.unique_ptr所指向(引用)的资源只能被unique_ptr所独占,不能被Copy,只能被转移。
2.自动析构不计数(结合一中的定义,独占型指针也没有任何计数的必要)
以上是unique_ptr的特点,这也是Rust中Box的主要特点:
只能转移所有权,不能Copy,同时只能有一个有效的Box。
Box的使用场景
由于Box的独占特点,其使用一般可以用在:
- 避免深拷贝一些多字节的数据
- 此文中提到的特征对象作为返回值使用,个人理解这是Box的最有用且最常用之处。
- 作为容器中得item如:
vec![Box<dyn Noise>] //比较适合在实践一些设计模式时使用
代码示例:
fn returns_noise(isdog: bool) -> Box<dyn Noise> {
if isdog {
Box::new(Dog {
voice: String::from(
"wangwang",
),
})
} else {
Box::new(Cat {
voice: String::from(
"miaomiao",
),
})
}
}
Rust中的智能指针Rc与Arc
Rc: 全称 Reference Count,即引用计数。
Arc: 全称Atomic Reference Count,即原子性引用计数。
由定义可知,Arc对比与Rc的一大优点就是原子性,既线程安全。而实现了线程安全势必要损失了一些性能,所以Rc比Arc性能要更好些,这两个智能指针都是只读的 。
Rc与C++中的Shared_ptr机制类似,都是通过引用计数和RAII最终实现对于堆内存的自动控制,两者都是线程不安全的,最大的区别便是Rc只读。
代码示例
{
let rc1 = Rc::new(String::from("hello world"));
let rc2 = Rc::clone(&rc1);
let rc3 = Rc::clone(&rc1);
}
{
let mystr = String::from("hello world");
let bx2 = Box::new(&mystr);
let bx3 = Box::new(&mystr);//got error
}
以上是Rc与Box间的对比,由于所有权的转移bx3在二次借用时便会出错,而rc拥有计数规则,上述代码将通过编译。
这里细心的同学会留意到clone,这里只是浅拷贝。
原则上,栈上数据基本都可以直接复制,而堆上内存申请性能相对较慢,堆上内存非必要情况下不做深拷贝,同理,如果你作为一个语言开发者,非必要情况下也不会默认将堆上内存直接深拷贝。
Arc是Rc的线程安全版本,用法函数基本一致,不做代码示例,有需要可用自行查阅。
rust中的RefCell
在之前我们提到过Rc与C++中的shared_ptr很接近,但是是只读的,如何做到内部可变 —> 结合RefCell。
内部可变:在不改变外部套壳的情况下,可更改内部数值。
let s = Rc::new(RefCell::new("hello ".to_string()));
let s1 = s.clone();
let s2 = s.clone();
s2.borrom_mut().push_str("world");
println!("{:?}",s);
println!("{:?}",s1);
println!("{:?}",s2);
均打印出 hello world
refcell的缺点:
代码使用了refcell后,rust被遵循的借用三大规则被移动到运行时,强制panic(也算比较安全,至少比unsafe从字面上的来看更舒服些)
rust中的weak
先来看看C++中的weak_ptr定义
std::weak_ptr is a smart pointer that holds a non-owning (“weak”) reference to an object that is managed by std::shared_ptr. It must be converted to std::shared_ptr in order to access the referenced object.
std::weak_ptr models temporary ownership: when an object needs to be accessed only if it exists, and it may be deleted at any time by someone else, std::weak_ptr is used to track the object, and it is converted to std::shared_ptr to acquire temporary ownership. If the original std::shared_ptr is destroyed at this time, the object’s lifetime is extended until the temporary std::shared_ptr is destroyed as well.
Another use for std::weak_ptr is to break reference cycles formed by objects managed by std::shared_ptr. If such cycle is orphaned (i.e., there are no outside shared pointers into the cycle), the shared_ptr reference counts cannot reach zero and the memory is leaked. To prevent this, one of the pointers in the cycle can be made weak.
总之,weak_ptr很弱,只记录状态信息,不保证一定存在,同时(主要)为了解决shared_ptr造成的循环引用,通常也不会单独出现,必须要转换成shared_ptr.
对比rust中的weak也是几乎一样的设计理由和使用条件,不保证引用一定存在,所以它返回Option< Rc < T > >,代码写法即upgrade升级到Rc,或将Rc降级到weak。
代码示例:
use std::rc::{Rc, Weak};
use std::cell::RefCell;
#[derive(Debug)]
struct Node {
value: i32,
parent: RefCell<Weak<Node>>,
children: RefCell<Vec<Rc<Node>>>,
}
fn main() {
let _leaf = Rc::new(Node {
value: 3,
parent: RefCell::new(Weak::new()),
children: RefCell::new(vec![]),
});
}
Deref和Drop
- Deref: 将引用中的实际值解出并使用,是Rust中最常见的隐式转换,如将String类型传入 &str入参时等等
- Drop: 释放资源,类似于析构函数,同样的,有默认实现,也有主动重写。
- Deref 是特征,一般开发者仅会为自定义的智能指针实现解引用特征。
- 解引用可递推,所以在隐式转换时常常有多层的解引用。
总结
本章结合题目非常适合链表练习。
如有勘误,敬请指出。