Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When is reference-counting needed in a single threaded application that doesn't model circular data structures? [duplicate]

Rust can handle reference counting very elegantly with Rc. It seems many members of the community prefer not to use this and instead use the ownership/borrowing semantics of the language. This results in simpler programs to write, but aside from circular references is it ever necessary?

Clearly across threads things get more complex, so to simplify what I'm trying to learn, "in a single threaded application is reference-counting ever required except as a write-time optimization?" Is this an anti-pattern at the higher levels of Rust skill?

like image 553
NO WAR WITH RUSSIA Avatar asked Sep 26 '21 19:09

NO WAR WITH RUSSIA


People also ask

What is reference count in programming?

In computer science, reference counting is a programming technique of storing the number of references, pointers, or handles to a resource, such as an object, a block of memory, disk space, and others. In garbage collection algorithms, reference counts may be used to deallocate objects that are no longer needed.

How to implement reference counting?

In each object, keep a count of the number of references to the object. (Add 1 when copying the reference, subtract 1 when clearing or changing a reference.) When the count drops to zero, kill the object.

Which one maintains a reference count in C++?

Reference counting is one such technique. This method is simply keeping an extra counter along with each object that is created. The counter is the number of references that exist to the object, in the C/C++ case this would how many pointers refer to this object.


Video Answer


2 Answers

I think that @kmdreko is basically correct, but an example that's harder to model without Rc would be something like filtering queries with "heavy" data, down into multiple owners. Basically, any time you deal with a "set" into multiple other filtered sets, where the filters may overlap.

For example, if you gathered 1000 images, and you invoked OpenCV (or whatever) to find in the original set which images had birds, and which images had a tree in them, it's plausible that the result sets overlap. So if you don't use Rc (or similar), you're either copying these potentially huge images (probably not wanted) or keeping references to them. OK, but then you can't ever free the original set of images, as it's what owns the images that the result sets are referencing. And while there are workarounds to that (only preserve an image when one or more filter says to), then that leads to deeply integrating potentially separate parts of your program because of the memory model you use. Or just use Rc and it's "accessible" by others easily, and whatever brought it in can be "done" with the set and free everything not referenced, while the parts of the set still used are still preserved.

I agree that Rc and related can be over-used. I encounter that all the freaking time in C++ and people throwing shared_ptr all over the place and think they're "good" at that point (until the application or DLL shuts down, and the calls of "why is my application crashing on exit all the time??" start coming). That said, even single-threaded, sometimes ownership needs to be shared, so it can be passed on to an unknown number of consumers for later.

Like with most tools, there are ways to work around certain parts, but that may cause more pain instead.

like image 55
Kevin Anderson Avatar answered Oct 21 '22 14:10

Kevin Anderson


You should use Rc when you need shared ownership. Using Arc is no different, but perhaps its more common since standard threading mechanisms like std::thread::spawn require ownership.

You would still use Rc in single-threaded environments if your data is more graph-like (which could have circular dependencies but doesn't have to). In such cases, you cannot use basic references. You could represent graph-like relationships in a normalized fashion and use indexes or ids in lieu of references, but that may not always be favorable or possible. Of course, if you're using Rc when a structure could be represented hierarchically, using Rc would be unnecessary.

In addition, there's many reasons why a external function or structure would require a generic type or function to be 'static. In those situations, either moving, clone()-ing or sharing (with Rc) is the solution.

I wouldn't say Rc itself an anti-pattern, its simply another tool, but there are sometimes ways to avoid it that would result in clearer relationships and/or better performance. I often see Rc used liberally by Rust newcomers who haven't fully grasped the borrow-checker.

like image 44
kmdreko Avatar answered Oct 21 '22 12:10

kmdreko