什么是 C++ 的 shared_ptr 的 Rust 等价物? [英] What is the Rust equivalent of C++'s shared_ptr?

查看:80
本文介绍了什么是 C++ 的 shared_ptr 的 Rust 等价物?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

为什么在 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 是否计划允许对对象的第二次引用而不会使第一个无效,还是我们应该习惯于只处理引用的引用?

解决方案

RcArcshared_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 不同,RcArc 具有更好的线程语义.shared_ptr 是半线程安全的.shared_ptr 的引用计数器本身是线程安全的,但共享数据并不是神奇地"使线程安全的.

如果您在线程程序中使用 shared_ptr,您仍然需要做更多的工作来确保它的安全.在非线程程序中,您需要为一些不需要的线程安全付费.

如果您希望允许改变共享值,您还需要切换到运行时借用检查.这由 CellRefCellMutex 等类型提供.RefCell 适用于 StringRc:

使用 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_ptrRcArc 都是维护计数器的运行时构造.

<块引用>

是否可以在不使第一个引用无效的情况下对对象进行引用?

这正是 Rust 对引用所做的,以及您已经完成的:

fn main() {让 a = String::from("ping");让 b = &a;println!("{{{}, {}}}", a, b);}

更好的是,只要 a 不再有效,编译器就会阻止您使用 b.

<块引用>

因为 Rust 的变量是通过引用而不是通过值复制的

这不是真的.当您分配一个值时,该值的所有权将转移到新变量.从语义上讲,变量的内存地址已更改,因此读取该地址可能会导致内存不安全.

<块引用>

我们是否应该养成只使用参考的习惯

是的,使用引用,尽可能,是最惯用的选择.这些需要零运行时开销,编译器会告诉您错误,而不是在运行时遇到错误.

当然有时 RcArc 很有用.通常循环数据结构需要它们.如果您无法获得工作的简单引用,您不应该对使用它们感到难过.

<块引用>

有参考文献吗?

这有点不利,因为额外的间接访问是不幸的.如果你真的需要,你可以减少它.如果不需要修改字符串,可以改用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);}

另见:

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:

这篇关于什么是 C++ 的 shared_ptr 的 Rust 等价物?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆