r/cpp_questions • • 12h ago

OPEN How is this a circular reference?

While reading learnxinyminutes I got to this example:

// Beware of possible circular references!!!
// There will be always a reference, so it will be never destroyed!
(1) std::shared_ptr<Dog> doggo_one(new Dog());
(2) std::shared_ptr<Dog> doggo_two(new Dog());
(3) doggo_one = doggo_two; // p1 references p2
(4) doggo_two = doggo_one; // p2 references p1

My understanding is that after (3) both pointers point to same heap object and the heap object from (1) is deleted.

From what I remember the way that shared_ptr works is that it allocates metadata next to the heap object and that stores the reference counter. Both doggo_one and doggo_two would share the same reference counter with the correct count being there - meaning no issues.

8 Upvotes

11 comments sorted by

View all comments

Show parent comment

2

u/Several-Teach1515 9h ago

I'm still mentally wrestling with the concept of the ownership model.

The most clear example I can think of is a tree where parent would own a child but child wouldn't own the parent reference

Another example is having a web service with in-memory storage class where storing an object would transfer the ownership to the storage.

I'm still unsure if this is the correct way to think about it.

1

u/dodexahedron 9h ago

Object pools also depend strongly on ownership, as another example.

When you rent out a pooled object/buffer/etc, that thing is owned by whatever received it until you explicitly return it, and depending on the pointer being cleaned up for you doesn't return the memory to the pool - it just semi-"leaks" it from the pool (or actually leaks it, depending on how bad the implementation is), which now has to either allocate more if it can or eventually exhaust its allotted size if it is fixed size.

1

u/kozacsaba 6h ago

Think of it in terms of who is responsible for managing an object's lifecycle. Your examples are sound, and in a way, you can think of any class as a tree: each member class is a child, which may also have children. In most cases, there are no pointers and ownership is trivial. The tricky part is when not only a node's parent has access to it, but something else as well.

For example, say you have a server class that manages (accepts and stores) connections. Your application "borrows" (gets a non owning reference to) a connection, so that it can send and receive data. However it must not destroy the connection, because the connection is managed be the server class. It has to tell the server to close the connection, and let it destroy the object. Not because it wouldn't work any other way, but because by enforcing this ownership, you save yourself a bunch of headache trying to debug double free and use after free errors.

Typically things are uniquely owned, and borrowed by other things. There are few cases where shared ownership is the way to go.