27_rust_智能指针
智能指针
智能指针是一种数据结构,其行为与指针类似,有额外的元数据和功能。
引用计数(reference counting)智能指针类型,通过记录所有者的数量,使一份数据被多个所有者同时持有,并在没任何所有者时自动清理数据。
其中引用只借用数据,而智能指针常拥有所指向的数据。如智能指针String 和 Vec
智能指针的实现:
- 智能指针通常使用 struct 实现, 并且实现了Deref和Drop这两个trait
- Deref trait: 允许智能指针 struct 的实例像引用一样使用
- Drop trait: 允许你自定义当智能指针实例走出作用域时的代码
标准库中常见的智能指针
- Box
:在heap内存上分配值 - Rc
:启用多重所有权的引用计数类型 - Ref
和 RefMut ,通过 RefCell 访问:在运行时而不是编译时强制借用规则的类型
其他内容:
- 内部可变模式(inferior mutability pattern):不可变类型暴露出可修改其内部值的 API
- 引用循环(reference cycles) 它们如何泄露内存,以及如何防止其发生
使用Box<T>
指向Heap上的数据
Box<T>
是最简单的智能指针,允许在heap上存储数据,形式为在stack上有一指针类型,存放了指向Heap数据的地址,无性能开销,也无其它额外功能,但实现了Deref trait和Drop trait,所以是智能指针。
Box<T>
的常用场景
- 在编译时,某类型的大小无法确定,但使用该类型时,上下文却需要知道确切大小
- 有大量数据,需要移交所有权,但需确保在操作时数据不会被复制
- 在使用某个值时,只关心是否实现了特定的trait,而不关心具体类型
fn main() {
let a = Box::new(3); // 3存在堆里
println!("{}", a);
} // 至此a的生命周期结束,会自动释放stack的指针,以及释放heap的数据内存
使用Box赋能递归类型
在编译时,rust需要知道一个类型所占的内存空间大小,而递归类型的大小无法在编译时确定,但Box类型的大小确定,在递归类型中使用Box就可解决上述问题。在函数式语言中叫Cons List。
使用Box来获得确定大小的递归类型
Box<T>
是一指针,指针本身的大小是固定的,指向的heap数据大小可不确定,这样编译时便可知需要多少内存。实际上就是c/c++语言中结构体和类中包含指针类型。
Box<T>
:
- 只提供了“间接”存储和heap内存分配的功能
- 没有其它额外功能也没有性能开销
- 适用于需要“间接”存储的场景,如Cons List
- 实现了Deref trait(使得能当引用处理) 和Drop trait(自动释放)
use crate::List::{Cons, Nil};
fn main() {
let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil))))));
}
enum List {
Cons(i32, Box<List>),
Nil,
}
Deref Trait
实现Deref Trait,可自定义解引用运算符*的行为。通过实现Deref,智能指针可像常规引用一样来处理。
解引用运算符:(常规引用也是一种指针)
fn main() {
let x = 3;
let y = &x;
println!("{}{}", x, *y);
}
上面代码要想获得y所指向的值,就需要加上*号解引用。
把Box<T>
代替上面代码中的引用:
fn main() {
let x = 3;
let y = Box::new(x);
assert_eq!(3, x);
assert_eq!(3, *y);
}
定义智能指针
Box<T>
被定义成拥有一个元素的tuple struct。
实现Deref Trait:标准库中的Deref trait要求实现一个deref方法,该方法借用self,并返回一个指向内部数据的引用。
use std::ops::Deref;
struct TestBox<T>(T); // 定义tuple
impl<T> TestBox<T> {
fn new(x: T) -> TestBox<T> {
TestBox(x)
}
}
impl<T> Deref for TestBox<T> {
type Target = T; // 定义关联类型
fn deref(&self) -> &T {
&self.0 // 解引用,返回tuple第0个元素
}
}
fn main() {
let x = 5;
let y = TestBox::new(x);
println!("Hello, world! {}", *y); // *y相当于*(y.deref())
assert_eq!(5, *y);
}
函数和方法的隐式解引用转化(Deref Coercion)
隐式解引用转化(Deref Coercion)是为函数和方法提供的一种便捷特性。
假设T实现了Deref trait,Deref Coercion 可以把T的引用转化为T经过Deref操作后生成的引用。
当把某类型的引用传递给函数或方法时,但它的类型与定义的参数类型不匹配,Deref Coercion 就会自动发生,编译器会对 deref进行一系列调用,来把它转为所需的参数类型,此过程在编译时完成,没有额外性能开销。
use std::ops::Deref;
struct TestBox<T>(T); // 定义tuple
impl<T> TestBox<T> {
fn new(x: T) -> TestBox<T> {
TestBox(x)
}
}
impl<T> Deref for TestBox<T> {
type Target = T; // 定义关联类型
fn deref(&self) -> &T {
&self.0 // 解引用,返回tuple第0个元素
}
}
fn print_func(nm: &str) {
println!("{}", nm);
}
fn main() {
let y = TestBox::new(String::from("hello rust"));
// 因为TestBox实现了Deref trait,会自动把TestBox的引用转换成string的引用
// => deref &String,又因标准库中String也实现了Deref trait,、
// 且返回值是&str,继续调用deref,最后与函数目标类型匹配。
print_func(&y);
// 如果没有Coercion功能,则需要按照如下方式传参
print_func(&(*y)[..]); //首先解引用,再取字符串引用,进行切片
}
因为Coercion功能,编译阶段会不断尝试调用deref,使得与目标类型匹配。
解引用与可变性
可使用DerefMut trait 重载可变引用的 * 运算符。
在类型和 trait 在下列三种情况发生时, Rust 会执行 deref coercion:
- 当 T: Deref<Target=U>, 允许 &T 转换为 &U
- 当 T: DerefMut<Target=U>, 允许 &mut T 转换为 &mut U
- 当 T: Deref<Target=U>, 允许 &mutT 转换为 &U,反过来不行
Drop trait
实现Drop trait,可让自定义当值将要离开作用域时发生的动作(类似于析构函数),比如文件、网络资源释放等,所有类型皆可实现Drop trait。
Drop trait只需实现drop方法,参数为对self的可变引用;Drop trait在预导入模块里(prelude),无需手动引入。
struct TestDrop {
s: String,
}
impl Drop for TestDrop {
fn drop(&mut self) {
println!("drop func, {}", self.s);
}
}
fn main() {
let s1 = TestDrop { s: String::from("string 1") };
let s2 = TestDrop { s: String::from("string 2") };
println!("start drop");
}
/*输出结果,与析构过程一样,与变量定义顺序相反
start drop
drop func, string 2
drop func, string 1
*/
使用std::mem::drop来提前drop值
如果上面例子使用s1.drop()尝试手动调用drop方法,编译时会报错“explicit destructor calls not allowed”,无法手动调用析构函数。
Drop trait的目的是进行自动释放处理逻辑的资源,很难直接禁用drop功能,也没必要,rust也不允许手动调用Drop trait的drop方法。
但可调用标准库的std::mem::drop函数(也在prelude中),以提前drop值。且编译器能保证只调用一次drop,手动调用后不会再自动调用。
struct TestDrop {
s: String,
}
impl Drop for TestDrop {
fn drop(&mut self) {
println!("drop func, {}", self.s);
}
}
fn main() {
let s1 = TestDrop { s: String::from("string 1") };
drop(s1);
let s2 = TestDrop { s: String::from("string 2") };
println!("start drop");
}
/*输出结果
drop func, string 1
start drop
drop func, string 2
*/
Rc<T>
引用计数智能指针
为了应付一个值被多处引用,拥有多个所有者的场景,rust引入了引用计数智能指针,类似于Java里的垃圾回收机制(引用计数法),使得rust能够支持多重所有权,Rc<T>
:
- reference couting(引用计数)
- 在实例内部维护一个记录引用次数的计数器,追踪所有到值的引用,从而判断值是否还在被使用
- 如果计数器减少到0,也就是有0个引用时,该值可被安全清理,而不会发生引用失效的问题
Rc<T>
使用场景:
- 需要在heap上分配数据,且数据被程序的多个部分读取(只读),但编译时无法确定哪个部分最后使用完这些数据。(如果可确定哪个最后使用完,直接让其成为所有者即可)
Rc<T>
只能用于单线程场景
Rc<T>
的基本信息:
Rc<T>
不知预导入模块(prelude)里- Rc::clone<&a>函数:增加引用计数
- Rc::strong_count(&a):获得强引用计数,还有Rc::weak_count函数获得弱引用计数
enum ListTest {// 定义一个链表枚举
Cons(i32, Box<ListTest>), // 变体Cons可存储数据,第一个是链表的数据,第二个则是指向下一个链表节点的引用
Nil // 第二个变体是链表的结束值:空
}
use crate::ListTest::{ Cons, Nil};
fn main() {
let a = Cons(5, Box::new(Cons(10, Box::new(Nil))));
let b = Cons(3, Box::new(a));
let c = Cons(6, Box::new(a)); //编译报错 use of moved value: `a`
}
修改使用Rc引用
enum ListTest {
Cons(i32, Rc<ListTest>),
Nil
}
use crate::ListTest::{ Cons, Nil}; // 引入枚举
use std::rc::Rc;
fn main() {
let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
// 如果a.clone()执行的是深拷贝
let b = Cons(3, Rc::clone(&a)); // b不再获得所有权,只增加引用计数
let c = Cons(6, Rc::clone(&a)); // rc clone仅增加引用计数,不会进行深拷贝
}
并可通过strong_count打印出强引用计数:
enum ListTest {
Cons(i32, Rc<ListTest>),
Nil
}
use crate::ListTest::{ Cons, Nil}; // 引入枚举
use std::rc::Rc;
fn main() {
let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
println!("a count after a ={}", Rc::strong_count(&a));
let b = Cons(3, Rc::clone(&a));
println!("a count after b ={}", Rc::strong_count(&a));
{
let c = Cons(6, Rc::clone(&a));
println!("a count after c ={}", Rc::strong_count(&a));
}
println!("a count leave c scope ={}", Rc::strong_count(&a));
}
/*打印结果
a count after a =1
a count after b =2
a count after c =3
a count leave c scope =2
*/
可见每增加一次引用,计数加1,离开一个引用的作用域计数减少1。
Rc::clone()与类型的clone()方法的区别:
- Rc::clone()增加引用,不会执行深拷贝
- 类型的clone()很多会执行深拷贝
Rc<T>
通过不可变引用,使得在程序不同部分之间共享只读数据。如果是可变引用的话,会因为多个可变引用改变数据,导致数据竞争不一致的问题。
RefCell<T>
和内部可变性
内部可变性 (interior mutability) :
内部可变性是 Rust 的设计模式之一,它允许在只持有不可变引用的前提下对数据进行修改。数据结构中使用了 unsafe 代码来绕过 Rust 正常的可变性和借用规则。
RefCell<T>
:
与Rc<T>
不同,RefCell<T>
类型代表其持有数据的唯一所有权。
借用规则:在任何给定时间内,要么只能拥有一个可变引用,要么只能拥有任意数量的不可变引用,引用总是有效的。
RefCell<T>
与Box<T>
的区别:
RefCell<T> |
Box<T> |
---|---|
只会在运行时检查借用规则 | 编译阶段强制代码遵守借用规则 |
否则触发panic | 否则出现错误 |
借用规则在不同阶段进行检查的比较:
编译阶段检查 | 运行时检查 |
---|---|
尽早暴露问题 | 问题暴露延后,甚至到生产环境 |
无任何运行时开销 | 因借用计数产生少许性能损失 |
对大多数场景是最佳选择,是rust的默认行为 | 实现某些特定的内存安全场景(不可变环境中修改自身数据) |
rust语言的编译器是非常保守的,即便有些代码可确定没问题,但写法上存在异常,编译器无法分析清楚,都不会让其通过,这样可保障代码可能繁琐些,但可保证运行时的安全性和稳定性,这也是rust语言的优势之一。
RefCell<T>
与Rc<T>
相似,只能用于单线程场景。
选择Box<T>
、Rc<T>
、RefCell<T>
的依据:
Box<T> |
Rc<T> |
RefCell<T> |
|
---|---|---|---|
同一个数据的所有者 | 一个 | 多个 | 一个 |
可变性、借用检查 | 可变、不可变借用(编译时检查) | 不可变借用(编译时检查) | 可变、不可变借用(运行时检查) |
其中:即便RefCell<T>
本身不可变,但仍能修改其存储的值。
内部可变性:可变的借用借用一个不可变的值
两个方法:
- borrow方法:返回智能指针
Ref<T>
,实现了Deref trait - borrow_mut方法:返回智能指针
RefMut<T>
,实现了Deref trait
例子:使用struct实现mock obj的测试打桩功能(rust里未提供打桩测试功能,可通过struct来实现该功能)
// lib.rs代码
pub trait Msg {
fn send(&self, msg: &str);
}
pub struct Limit<'a, T: 'a + Msg> {
msg: &'a T,
v: usize,
max: usize,
}
impl<'a, T> Limit<'a, T>
where
T: Msg,
{
pub fn new(msg: &T, max: usize) -> Limit<T> {
Limit { msg, v: 0, max, }
}
pub fn set_val(&mut self, v: usize) {
self.v = v;
let m = self.v as f64 / self.max as f64;
if m >= 1.0 {
self.msg.send("Err: >= 1");
} else if m >= 0.9 {
self.msg.send("Warn: over 0.9");
} else if m >= 0.7 {
self.msg.send("Warn: over 0.7");
}
}
}
// 需要测试,传入不同value的时候,会发送不同msg,测试则通过存储发送的msg,检查是否符合预期
#[cfg(test)]
mod tests {
use super::*;
use std::cell::RefCell;
struct MockMsg {
sent_msg: RefCell<Vec<String>>,
}
impl MockMsg {
fn new() -> MockMsg {
MockMsg { sent_msg: RefCell::new(vec![]), }
}
}
impl Msg for MockMsg {
fn send(&self, message: &str) { // borrow_mut获得内部值的可变引用
self.sent_msg.borrow_mut().push(String::from(message));
}
}
#[test]
fn test1() {
let mmsg = MockMsg::new();
let mut l = Limit::new(&mmsg, 100);
l.set_val(80);
assert_eq!(mmsg.sent_msg.borrow().len(), 1); //获得不可变引用
}
}
//运行cargo test测试通过
RefCell<T>
会记录当前存在多少个活跃的Ref<T>
和RefMut<T>
智能指针:
- 每次调用borrow,不可变借用计数加1
- 任何一个
Ref<T>
的值离开作用域被释放时,不可变借用计数减1 - 每次调用borrow_mut,可变借用计数加1
- 任何一个
RefMut<T>
的值离开作用域被释放时,可变借用计数减1
维护借用检查的规则:
- 任何一个给定时间里,只允许拥有多个不可变借用和一个可变借用,违背这个规则时将在运行时panic。
Rc<T>
和RefCell<T>
结合使用,实现一个拥有多重所有权的可变数据
#[derive(Debug)]
enum ListTest {
Cons(Rc<RefCell<i32>>, Rc<ListTest>),
Nil
}
use crate::ListTest::{ Cons, Nil}; // 引入枚举
use std::rc::Rc;
use std::cell::RefCell;
fn main() {
let v = Rc::new(RefCell::new(5));
let a = Rc::new(Cons(Rc::clone(&v), Rc::new(Nil)));
let b = Cons(Rc::new(RefCell::new(6)), Rc::clone(&a));
let c = Cons(Rc::new(RefCell::new(7)), Rc::clone(&a));
*v.borrow_mut() += 5;
println!("a={:?},\nb={:?},\nc={:?}", a, b, c);
}
/*输出结果:
a=Cons(RefCell { value: 10 }, Nil),
b=Cons(RefCell { value: 6 }, Cons(RefCell { value: 10 }, Nil)),
c=Cons(RefCell { value: 7 }, Cons(RefCell { value: 10 }, Nil))
*/
其它可实现内部可变性的类型:
Cell<T>
:通过赋值来访问数据
Mutex<T>
:用于实现跨线程情形下的内部可变性模式
循环引用可导致内存泄漏
rust提供了可靠的安全保障,使得程序很难发生内存泄露。但如使用Rc<T>
和RefCell<T>
创造出循环引用,而导致内存泄漏,因为每个项的引用数量不会变成0,值不会被释放。简单理解就是一个两个元素的循环链表,相互拥有指向对方的智能指针,使得计数不会减少到0。
use crate::ListTest::{Cons, Nil};
use std::cell::RefCell;
use std::rc::Rc;
#[derive(Debug)]
enum ListTest {
Cons(i32, RefCell<Rc<ListTest>>), // 用于指向下一个元素
Nil
}
impl ListTest {
fn tail(&self) -> Option<&RefCell<Rc<ListTest>>> {
match self { // 用于返回第二个元素
Cons(_, item) => Some(item),
Nil => None,
}
}
}
fn main() {
let a = Rc::new(Cons(5, RefCell::new(Rc::new(Nil)))); // 第一个元素5,第二个元素nil
println!("a rc cnt={}, a->next{:?}", Rc::strong_count(&a), a.tail());
let b = Rc::new(Cons(10, RefCell::new(Rc::clone(&a)))); // 使得b指向a有个引用
println!("a rc cnt={}, b cnt={}, b->next{:?}", Rc::strong_count(&a),
Rc::strong_count(&b), b.tail());
if let Some(l) = a.tail() { // 取出a的第二个元素
*l.borrow_mut() = Rc::clone(&b); // 通过borrow mut将里边存储的Nil值改成b的引用
}
println!("a rc cnt={}, b cnt={}", Rc::strong_count(&a), Rc::strong_count(&b));
}
/*打印结果
a rc cnt=1, a->nextSome(RefCell { value: Nil })
a rc cnt=2, b cnt=1, b->nextSome(RefCell { value: Cons(5, RefCell { value: Nil }) })
a rc cnt=2, b cnt=2
*/
上述例子各自计数都为2,构成了一个双向链表:
上面的代码main函数走完时,首先会释放b,因为b是后创建的,会将b的引用计数减少到1,因为a中还有个指向b的引用,所以b在内存上并不会释放,a释放时也同理。
use crate::ListTest::{Cons, Nil};
use std::cell::RefCell;
use std::rc::Rc;
#[derive(Debug)]
enum ListTest {
Cons(i32, RefCell<Rc<ListTest>>), // 用于指向下一个元素
Nil
}
impl ListTest {
fn tail(&self) -> Option<&RefCell<Rc<ListTest>>> {
match self { // 用于返回第二个元素
Cons(_, item) => Some(item),
Nil => None,
}
}
}
fn main() {
let a = Rc::new(Cons(5, RefCell::new(Rc::new(Nil)))); // 第一个元素5,第二个元素nil
println!("a rc cnt={}, a->next{:?}", Rc::strong_count(&a), a.tail());
let b = Rc::new(Cons(10, RefCell::new(Rc::clone(&a)))); // 使得b指向a有个引用
println!("a rc cnt={}, b cnt={}, b->next{:?}", Rc::strong_count(&a),
Rc::strong_count(&b), b.tail());
if let Some(l) = a.tail() { // 取出a的第二个元素
*l.borrow_mut() = Rc::clone(&b); // 通过borrow mut将里边存储的Nil值改成b的引用
}
println!("a rc cnt={}, b cnt={}", Rc::strong_count(&a), Rc::strong_count(&b));
println!("a->next{:?}", a.tail()); // 这条将不断循环打印,直到溢出
}
/*输出:
....
ue: Cons(5,
thread 'main' has overflowed its stack
*/
增加一行打印后,可见堆栈溢出错误。
虽然上述例子是刻意构造出来的内存溢出,但也说明是存在此种可能的。
防止内存泄露的解决办法:
- 依靠开发者保证,不能完全靠rust编译器。
- 重新组织数据结构,一些引用来表达所有权,一些引用不表达所有权,循环引用中的一部分具有所有权关系,另一部分不涉及所有权关系,而只有所有权关系才影响值的清理。
为了防止循环引用,把Rc<T>
换成Weak<T>
。
弱引用Weak<T>
把Rc<T>
换成Weak<T>
,Rc::clone为Rc<T>
实例的strong_count加1,Rc<T>
的实例只有在strong_count为0的时候才会被清理。Rc<T>
实例通过调用Rc::downgrade方法可创建值的Weak Reference(弱引用),返回类型是Weak<T>
智能指针,调用Rc::downgrade会为weak_count加1.
Rc<T>
使用weak_count来追踪存在多少Weak<T>
- weak_count不为0并不影响
Rc<T>
实例的清理
强引用(Stong Reference)是关于如何分享Rc<T>
实例的所有权,弱引用(Weak Reference)并不获得所有权。使用弱引用不会创建循环引用,因为当强引用计数为0时,弱引用会自动断开。在使用Weak<T>
前,需要保证其指向的值仍然存在,可在Weak<T>
实例上调用upgrade方法,返回Option<Rc<T>>
确认是否还在。
例子:创建一个树,父节点通过next能找到子节点,子节点也能通过parent找到父节点,类似于循环引用
use std::rc::{Rc, Weak};
use std::cell::RefCell;
#[derive(Debug)]
struct Node { // 一个树节点的数据结构
v: i32,
parent: RefCell<Weak<Node>>, // 指向父节点是一个弱引用,防止之前的循环引用的问题
next: RefCell<Vec<Rc<Node>>>, // 指向下一层(孩子)节点集合,树的结构,下一层可能有多个节点
}
fn main() {
let level2 = Rc::new(Node { // 先创建子节点
v: 6,
parent: RefCell::new(Weak::new()), // 暂时先设成空引用
next: RefCell::new(vec![]), // 下一层子节点集合是一个空的vector
});
// 先打印parent,当前应是None,先用borrow获得不可变引用,再用upgrade将Weak<T>转成Rc<T>
println!("parent={:?}", level2.parent.borrow().upgrade());
let level1 = Rc::new(Node { // 创建父节点,第一层
v: 8,
parent: RefCell::new(Weak::new()),
next: RefCell::new(vec![Rc::clone(&level2)]), //下一层指向level2节点
});
// 让level2的parent指向上一层level1,获得可变引用,再通过downgrade将Rc<T>转成Weak<T>
*level2.parent.borrow_mut() = Rc::downgrade(&level1);
println!("parent={:?}", level2.parent.borrow().upgrade());
}
/*打印结果
parent=None
parent=Some(Node { v: 8, parent: RefCell { value: (Weak) }, next: RefCell { value: [Node { v: 6, parent: RefCell { value: (Weak) }, next: RefCell { value: [] } }] } })
*/
进一步打印出强引用和弱引用计数,可看出Weak count虽然为1,离开作用域依然可正常释放level1节点
use std::rc::{Rc, Weak};
use std::cell::RefCell;
#[derive(Debug)]
struct Node { // 一个树节点的数据结构
v: i32,
parent: RefCell<Weak<Node>>, // 指向父节点是一个弱引用,防止之前的循环引用的问题
next: RefCell<Vec<Rc<Node>>>, // 指向下一层(孩子)节点集合,树的结构,下一层可能有多个节点
}
fn main() {
let level2 = Rc::new(Node { // 先创建子节点
v: 6,
parent: RefCell::new(Weak::new()), // 暂时先设成空引用
next: RefCell::new(vec![]), // 下一层子节点集合是一个空的vector
});
// 打印出level2的强引用和弱引用
println!("level2 strong={}, weak={}", Rc::strong_count(&level2), Rc::weak_count(&level2));
{ // 创建出一个内部作用域
let level1 = Rc::new(Node { // 创建父节点,第一层
v: 8,
parent: RefCell::new(Weak::new()),
next: RefCell::new(vec![Rc::clone(&level2)]), //下一层指向level2节点,level2的强引用计数加1
});
// 让level2的parent指向上一层level1,获得可变引用,再通过downgrade将Rc<T>转成Weak<T>
*level2.parent.borrow_mut() = Rc::downgrade(&level1);
// 建立parent关系后,打印计数
println!("level2 strong={}, weak={}", Rc::strong_count(&level2), Rc::weak_count(&level2));
println!("level1 strong={}, weak={}", Rc::strong_count(&level1), Rc::weak_count(&level1));
} // 离开这个作用域,level1强引用计数减到0,释放,level2的强引用计数也减1
println!("level2 strong={}, weak={}", Rc::strong_count(&level2), Rc::weak_count(&level2));
println!("parent={:?}", level2.parent.borrow().upgrade());
}
执行cargo run结果:
level2 strong=1, weak=0
level2 strong=2, weak=0 # 创建level1节点后,next指向level2,使其强引用计数+1
level1 strong=1, weak=1 # 此时因level2的parent指向level1,且为弱引用,所以weak count为1,
level2 strong=1, weak=0 # 离开作用域后,level1释放,level2的强引用计数也减1
parent=None
可见弱引用使用前需要判断是否为None,因为所指向的节点可能已经释放。