Why is the shared pointer not going out of scope at end of main?

Viewed 573

I have deliberately introduced a circular dependency in below code.

My doubt is that since when the shared pointer w goes out of scope, then the ref count is not zero and hence Widget object won't be destroyed.

But at the end of main, won't the shared pointer ' w->mGadget->mWidget' too go out of scope, since everything is known to cease to exist at the end of main? I am bit confused about the scope here. I was expecting that all entity scope should get over when main exits. Where is the missing link in my understanding here?

#include <memory>
#include <iostream>

struct Gadget;

struct Widget
{
    std::shared_ptr<Gadget> mGadget;
};

struct Gadget
{
    std::shared_ptr<Widget> mWidget;
};

int main()
{
    auto w = std::make_shared<Widget>();
    w->mGadget = std::make_shared<Gadget>();
    w->mGadget->mWidget = w;
    return 0;
}
2 Answers

You are confusing the lifetimes of the shared_ptrs with the lifetimes of the objects they refer to.

std::shared_ptr maintains a pointer to an object, and associates that pointer with a reference count. shared_ptr does not destroy the pointed object until the reference count falls to 0.

  • auto w = std::make_shared<Widget>();
    This creates a new Widget object, and then creates a new shared_ptr that points to it, and sets the shared_ptr's reference count to 1.

  • w->mGadget = std::make_shared<Gadget>();
    This creates a new Gadget object, and then creates a new shared_ptr that points to it, and sets the shared_ptr's reference count to 1.

  • w->mGadget->mWidget = w;
    This assigns w to the Gadget object's mWidget member, incrementing the reference count of the Widget pointer to 2.

Now, when main() exits, the only variable that goes out of scope is w, so that is the only variable destroyed, decrementing the reference count of the Widget pointer to 1 not 0. And since there is still an active reference to that pointer, the Widget object is not destroyed. And since its mGadget member has an active reference to the Gadget pointer, the Gadget object is not destroyed either.

To solve this, you need to change the Gadget::mWidget member to std::weak_ptr instead:

#include <memory>

struct Gadget;

struct Widget
{
    std::shared_ptr<Gadget> mGadget;
};

struct Gadget
{
    std::weak_ptr<Widget> mWidget; // <-- here
};

int main()
{
    auto w = std::make_shared<Widget>();
    w->mGadget = std::make_shared<Gadget>();
    w->mGadget->mWidget = w;
    return 0;
}

std::weak_ptr does not increment a std::shared_ptr's reference count (at least until std::weak_ptr::lock() is called), so the Widget pointer's reference count will be 1 not 2. So when w goes out of scope and is destroyed, the reference count of the Widget pointer falls to 0, destroying the Widget object, which will destroy its mGadget member, which will decrement the reference count of the Gadget pointer to 0, destroying the Gadget object.

There are three std::shared_ptrs here:

  1. w in main
  2. mGadget
  3. mWidget

The latter two are members of objects that were created in dynamic scope.

w goes out of scope in main(), as any other automatically-scoped object. But mGadget and mWidget are left, each referencing the other's object. These two objects were created in dynamic scope.

This circular reference prevents either object's last reference from going out of scope and getting destroyed. A mutually-assured stalemate.

Related