什么是 C++ 的 shared_ptr 的 Rust 等价物? [英] What is the Rust equivalent of C++'s shared_ptr?
问题描述
为什么在 Rust 中不允许这种语法:
fn main() {让 a = String::from("ping");让 b = a;println!("{{{}, {}}}", a, b);}
当我试图编译这段代码时,我得到了:
<块引用>error[E0382]:使用移动值:`a`-->src/main.rs:5:28|3 |让 b = a;|- 值移到这里4 |5 |println!("{{{}, {}}}", a, b);|^ 移动后此处使用的值|= 注意:发生移动是因为 `a` 的类型为 `std::string::String`,它没有实现 `Copy` trait
事实上,我们可以简单地做一个引用——这在运行时是不一样的:
fn main() {让 a = String::from("ping");让 b = &a;println!("{{{}, {}}}", a, b);}
它有效:
<块引用>{ping, ping}
根据
我们必须做这样的事情:
我喜欢通过引用复制的想法,但为什么会自动使第一个无效?
使用不同的方法应该可以避免双重释放.例如,C++ 已经有一个很棒的工具来允许多次免费调用......shared_ptr
仅在没有其他指针指向该对象时才调用 free - 它似乎与我们实际所做的非常相似,区别在于 shared_ptr
有一个计数器.
例如,我们可以在编译期间统计对每个对象的引用次数,只有在最后一个引用超出范围时才调用free
.
但 Rust 是一门年轻的语言;也许他们没有时间实施类似的东西?Rust 是否计划允许对对象的第二次引用而不会使第一个无效,还是我们应该习惯于只处理引用的引用?
Rc
或 Arc
是 shared_ptr
的替代品.您选择哪个取决于共享数据所需的线程安全级别;Rc
用于非线程情况,Arc
用于需要线程时:
使用 std::rc::Rc;fn 主(){让 a = Rc::new(String::from("ping"));让 b = a.clone();println!("{{{}, {}}}", a, b);}
与shared_ptr
一样,它不会复制String
本身.它仅在运行时在调用 clone
时增加引用计数器,并在每个副本超出范围时减少计数器.
与 shared_ptr
不同,Rc
和 Arc
具有更好的线程语义.shared_ptr
是半线程安全的.shared_ptr
的引用计数器本身是线程安全的,但共享数据并不是神奇地"使线程安全的.
如果您在线程程序中使用 shared_ptr
,您仍然需要做更多的工作来确保它的安全.在非线程程序中,您需要为一些不需要的线程安全付费.
如果您希望允许改变共享值,您还需要切换到运行时借用检查.这由 Cell
、RefCell
、Mutex
等类型提供.RefCell
适用于 String
和 Rc
:
使用 std::cell::RefCell;使用 std::rc::Rc;fn 主(){让 a = Rc::new(RefCell::new(String::from("ping")));让 b = a.clone();println!("{{{}, {}}}", a.borrow(), b.borrow());a.borrow_mut().push_str("pong");println!("{{{}, {}}}", a.borrow(), b.borrow());}
<块引用>
我们可以在编译期间统计对每个对象的引用次数,只有当最后一个引用超出范围时才调用 free.
这几乎就是 Rust 对引用所做的.它实际上不使用计数器,但它只允许您使用对值的引用,同时保证该值保持在相同的内存地址.
C++ 的 shared_ptr
在编译时不做到这一点.shared_ptr
、Rc
和 Arc
都是维护计数器的运行时构造.
是否可以在不使第一个引用无效的情况下对对象进行引用?
这正是 Rust 对引用所做的,以及您已经完成的:
fn main() {让 a = String::from("ping");让 b = &a;println!("{{{}, {}}}", a, b);}
更好的是,只要 a
不再有效,编译器就会阻止您使用 b
.
因为 Rust 的变量是通过引用而不是通过值复制的
这不是真的.当您分配一个值时,该值的所有权将转移到新变量.从语义上讲,变量的内存地址已更改,因此读取该地址可能会导致内存不安全.
<块引用>我们是否应该养成只使用参考的习惯
是的,使用引用,尽可能,是最惯用的选择.这些需要零运行时开销,编译器会告诉您错误,而不是在运行时遇到错误.
当然有时 Rc
或 Arc
很有用.通常循环数据结构需要它们.如果您无法获得工作的简单引用,您不应该对使用它们感到难过.
有参考文献吗?
这有点不利,因为额外的间接访问是不幸的.如果你真的需要,你可以减少它.如果不需要修改字符串,可以改用Rc
:
使用 std::rc::Rc;fn 主(){让a:Rc str ;= Rc::from("ping");让 b = a.clone();println!("{{{}, {}}}", a, b);}
如果您有时需要保持修改String
的能力,您也可以将&Rc
显式转换为&T代码>:
使用 std::rc::Rc;fn 主(){让 a = Rc::new(String::from("ping"));让 b = a.clone();让 a_s: &str = &*a;让 b_s: &str = &*b;println!("{{{}, {}}}", a_s, b_s);}
另见:
- 拥有多个强引用并允许可变性的正确智能指针是什么?
- 当我可以使用 Cell 或 RefCell 时,我应该选择哪个?
- Cell 或 RefCell 是最佳选择的情况
- 为什么不鼓励接受对 String (&String)、Vec (&Vec) 或 Box (&Vec) 的引用;Box) 作为函数参数?
- 如何构建 Rc
或 Rc[T]>? - CppCon 2017:Louis Brandy在 Facebook 上反复出现的 C++ 奇怪错误"
Why is this syntax not allowed in Rust:
fn main() {
let a = String::from("ping");
let b = a;
println!("{{{}, {}}}", a, b);
}
When I tried to compile this code, I got:
error[E0382]: use of moved value: `a` --> src/main.rs:5:28 | 3 | let b = a; | - value moved here 4 | 5 | println!("{{{}, {}}}", a, b); | ^ value used here after move | = note: move occurs because `a` has type `std::string::String`, which does not implement the `Copy` trait
In fact, we can simply make a reference - which does not the same at runtime:
fn main() {
let a = String::from("ping");
let b = &a;
println!("{{{}, {}}}", a, b);
}
And it works:
{ping, ping}
According to the Rust Book it is to avoid double free bugs because Rust's variables are copied by reference instead of by value. Rust will simply invalidate the first object and make it unusable...
We have to do something like this:
I like the idea of copying by reference, but why automatically invalidate the first one?
It should be possible to avoid the double free with a different method. For example, C++ already has a great tool to allow multiple free calls... The shared_ptr
calls free only when no other pointer points to the object - it seems to be very similar to what we are actually doing, with the difference that shared_ptr
has a counter.
For example, we can count the number of references to each object during the compilation time and call free
only when the last reference goes out of the scope.
But Rust is a young language; maybe they haven't had the time to implement something similar? Has Rust planned to allow a second reference to an object without invalidate the first one or should we take the habit to only work with a reference of a reference?
Either Rc
or Arc
is the replacement for shared_ptr
. Which you choose depends on what level of thread-safety you need for the shared data; Rc
is for non-threaded cases and Arc
is when you need threads:
use std::rc::Rc;
fn main() {
let a = Rc::new(String::from("ping"));
let b = a.clone();
println!("{{{}, {}}}", a, b);
}
Like shared_ptr
, this does not copy the String
itself. It only increases a reference counter at runtime when clone
is called and decreases the counter when each copy goes out of scope.
Unlike shared_ptr
, Rc
and Arc
have better thread semantics. shared_ptr
is semi-thread-safe. shared_ptr
's reference counter itself is thread-safe, but the shared data is not "magically" made thread safe.
If you use shared_ptr
in a threaded program, you still have more work to do to ensure it's safe. In a non-threaded program, you are paying for some thread-safety you don't need.
If you wish to allow mutating the shared value, you will need to switch to runtime borrow checking as well. This is provided by types like Cell
, RefCell
, Mutex
etc. RefCell
is appropriate for String
and Rc
:
use std::cell::RefCell;
use std::rc::Rc;
fn main() {
let a = Rc::new(RefCell::new(String::from("ping")));
let b = a.clone();
println!("{{{}, {}}}", a.borrow(), b.borrow());
a.borrow_mut().push_str("pong");
println!("{{{}, {}}}", a.borrow(), b.borrow());
}
we can count the number of references to each object during the compilation time and call free only when the last reference goes out of the scope.
That's almost exactly what Rust does with references. It doesn't actually use a counter, but it only lets you use references to a value while that value is guaranteed to remain at the same memory address.
C++'s shared_ptr
does not do this at compile-time. shared_ptr
, Rc
, and Arc
are all runtime constructs that maintain a counter.
Is it possible to make a reference to the object without invalidate the first reference?
That's exactly what Rust does with references, and what you've already done:
fn main() {
let a = String::from("ping");
let b = &a;
println!("{{{}, {}}}", a, b);
}
Even better, the compiler will stop you from using b
as soon as a
is no longer valid.
because Rust's variables are copied by reference instead of by value
This is not true. When you assign a value, ownership of the value is transferred to the new variable. Semantically, the memory address of the variable has changed and thus reading that address could lead to memory unsafety.
should we take the habit to only work with a reference
Yes, using references, when possible, is the most idiomatic choice. These require zero runtime overhead and the compiler will tell you about errors, as opposed to encountering them at runtime.
There's certainly times where Rc
or Arc
are useful. Often they are needed for cyclic data structures. You shouldn't feel bad about using them if you cannot get plain references to work.
with a reference of a reference?
This is a bit of a downside, as the extra indirection is unfortunate. If you really needed to, you can reduce it. If you don't need to modify the string, you can switch to an Rc<str>
instead:
use std::rc::Rc;
fn main() {
let a: Rc<str> = Rc::from("ping");
let b = a.clone();
println!("{{{}, {}}}", a, b);
}
If you need to keep the ability to modify the String
sometimes, you can also explicitly convert a &Rc<T>
to a &T
:
use std::rc::Rc;
fn main() {
let a = Rc::new(String::from("ping"));
let b = a.clone();
let a_s: &str = &*a;
let b_s: &str = &*b;
println!("{{{}, {}}}", a_s, b_s);
}
See also:
- What is the right smart pointer to have multiple strong references and allow mutability?
- When I can use either Cell or RefCell, which should I choose?
- Situations where Cell or RefCell is the best choice
- Why is it discouraged to accept a reference to a String (&String), Vec (&Vec) or Box (&Box) as a function argument?
- How to build an Rc<str> or Rc<[T]>?
- CppCon 2017: Louis Brandy "Curiously Recurring C++ Bugs at Facebook"
这篇关于什么是 C++ 的 shared_ptr 的 Rust 等价物?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!