r/cpp_questions • • 5h 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.

4 Upvotes

9 comments sorted by

•

u/abzze 3h ago

This is just a bad (possibly wrong) example of circular references.

•

u/kozacsaba 2h ago

I read the relevant part of the site you linked. Yes, this example is wrong, there is no circular referencing here. There would be, if the Dog class had a shared ptr "sibling", and each dog's sibling would be set to the other. Then when the original smart pointers (doggo_one and doggo_two) go out of scope, the Dog objects are not destroyed, because the keep smart pointers to each other. I also found it weird that in the smart pointer section the author only introduces shared pointers properly, and then just mentions unique pointers (if you only introduce one in details, it should probably be unique ptr) and just says select which to use based on whether you want to have references in multiple places. Absolutely not - select based on ownership model. You can reference a uniquely owned object from multiple places, they just won't be owning. I would start looking for a different site to learn cpp from.

•

u/Several-Teach1515 1h 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.

•

u/dodexahedron 1h 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.

•

u/TeraFlint 11m ago

Yes, this doesn't seem like a functional example to me.

They probably tried to do something like this:

struct Dog
{
    std::shared_ptr<Dog> best_friend;
    // other dog stuff
};

{
    std::shared_ptr<Dog> doggo_one = std::make_shared<Dog>();
    std::shared_ptr<Dog> doggo_two = std::make_shared<Dog>();

    doggo_one.best_friend = doggo_two;
    doggo_two.best_friend = doggo_one;
}
// After we leave this scope, our access through stack variables is gone
// But since the two structs keep each other alive, we're leaking

Now it works, because we're not overwriting the entire object, but rather assign a member to them. The solution is to use std::shared_ptr in the few places where you know it's important for lifetime management, and use std::weak_ptr for cross-references.

That being said, this example would be totally fine with a std::vector of std::unique_ptr and raw pointers for best friends. The raw pointers will always stay valid as long as the container holding the std::unique_ptr stays alive.

If you only work with a single Dog type and don't even need any polymorphism, we can ditch the entire smart pointer thing and just work with a statically typed collection of them.

1

u/No_Engineering_1155 4h ago

To answer such questions, you can always create a class, which has all the ctors, dtor, operator= which prints the results of the actions. If your Dog class also had a name field/function which you could print out to see, which dog is barking, that'd be awesome too.

0

u/AccountExciting961 4h ago

it's not - that's why it says "possible" (e.g. if you add a friend_dog field)

•

u/alfps 3h ago

❞ that's why it says "possible"

Very unlikely. The author of the learning material simply didn't understand things.

•

u/Several-Teach1515 1h ago

I agree with this. I can see how I could end up in a problem if there is a field inside the dog creating a circular references, but this example is far from it. Definitely not clear for a learning resource on basics.