Box and Rc and their equivalents in C++

Would I be right in saying that:
Box is a equivalent of C++ std::unique_ptr?
Rc is a equivalent of C++ std::shared_ptr?

Close but not actually. std::unique_ptr<T> is more like Option<Box<T>> in Rust and std::sharrd_ptr<T> is Option<Arc<T>>.

C++ doesn't have real move semantics. It only have another kind of reference and convention so programmers should implement this convention using that reference. std::move() is Option::take() amd invariants are managed by code.

Box<T> never get nulled on move, since the compiler can track every moves statically. Double move is compile error and the destructor is only called if the box isn't moved out.

Speaking with the Rc<T>, Rust is the only language which has both parallel thread and non-atomic reference counted type. It cannot be practical in C++ because modifying the counter from different threads would be UB. It's not a footgun, it's more like a gun-welded-towards-your-foot.

It's OK for Rust. If you tries to shoot your foot, the compiler throws error so it can't be compiled. But why would we need a non-atomic RC type? Because atomic operation is more expensive than the non-atomic one. Modern multicore processor is a complex asynchronous system and atomic operations involves some kind of synchronization between cores. For architectures with weaker memory model like ARM this can be more significant.

3 Likes

Sometimes shared_ptr isn't actually atomic:

1 Like

I've never implied that shared_ptr is atomic. Just like Rc is not. That's why I compared them to each other.

I actually am not sure what you're trying to say.
std::shared_ptr isn't atomic so cannot be compared to Arc. If anything std::shared_ptr can be compared to Rc which also isn't thread safe.

std::shared_ptr is "atomic when necessary" in some implementations. The standard requires it be thread safe. Arc is the proper comparison, as far as contracts/APIs are concerned.

1 Like

No it is not. shared_ptr is not atomic in the sense Arc is. That's why c++20 comes with:
std::atomic_shared_ptr
Is Arc also atomic when necessary or always?

How do you think people use shared_ptr with threads before C++20? Also note this:

To satisfy thread safety requirements, the reference counters are typically incremented using an equivalent of std::atomic::fetch_add with std::memory_order_relaxed (decrementing requires stronger ordering to safely destroy the control block).

std::shared_ptr doesn't requires its implementation uses atomics, but it requires that all member functions can be called from multiple threads. Rust's Rc is not safe to be used between different threads, the compiler throws error if such usage is detected.

It seems the std::atomic_shared_ptr is intended to be a wrapper over std::shared_ptr to provide similar functionality to the arc_swap crate. It's not a replacement, just different purpose.

2 Likes

How do you think people used multithreading before c++11? Pointless discussion. std::shared_ptr is not atomic in the same way Arc is. Unless you disagree with that and can provide some source that would prove your point.

You cannot safely from different threads modify object via std::shared_ptr unless you are going to use workarounds in form of for example:
std::atomic_store and the others.
On the other hand via Arc you can.
So basically as I understand, the closest equivalent to shared_ptr is Rc, and to std::atomic_shared_ptr Arc.

Arc doesn't let you modify an object from different threads, either. The key distinction here is that both Arc and shared_ptr let you change the refcount from different threads.

Arc<T> doesn’t allow modification of the contained T unless it’s the last strong reference to that object. To get the ability to alter the contents when there are multiple live references, you need to explicitly use one of the interior-mutability types (like Mutex) inside the Arc.

Yeah, you are right, the ref count is important. OK, thanks.

Got it. Thanks.

1 Like

Please check again the api of the std::atomic_shared_ptr. It's a storage for std::shared_ptr which can be replaced atomically. That's why I mentioned the arc_swap crate which provides similar interface and functionality.