silentwf
silentwf

Reputation: 153

c++ weak_ptr expiring after dereference?

I'm new to smart pointers and I'm trying to wrap around my head why a weak_ptr would expire after a dereference operator. The code I used to test is here:

#include <memory>
#include <iostream>
#include <vector>

using namespace std;

struct node
{
    weak_ptr<node> parent;
    shared_ptr<node> child;
    int val;
};

shared_ptr<node> foo()
{
    shared_ptr<node> a = make_shared<node>();
    shared_ptr<node> b = make_shared<node>();

    a->val = 30;
    b->val = 20;

    b->parent = a;
    a->child = b;

    return a;
}

int main()
{
    shared_ptr<node> c = foo();
    node d = *foo();

    if (c->child->parent.expired())
    {
        cout << "weak ptr in c has expired." << endl;
    }

    if (d.child->parent.expired())
    {
        cout << "weak ptr in d has expired." << endl;
    }

    return 0;
}

The program outputs weak ptr in d has expired.

I don't understand why when d uses the dereference operator, it expires. In regards to this, is there anyway to prevent it (other than not dereferencing it)?


I tried as mrtnj suggested by changing the weak_ptr in node to shared_ptr but I think I have a memory leak. I changed the node class to

struct node
{
    shared_ptr<node> parent;
    shared_ptr<node> child;
    int val;
};

and then modified the source code to add a tryCreate function.

void tryCreate()
{
    node d = *foo();
}

and then called it in my main such that my main looks like

int main()
{
    tryCreate();
    return 0;
}

I used Visual Studio 2015's memory profiling and noticed that there were only allocations and no deallocations. I changed parent into a weak_ptr and I see deallocations. Am I doing something wrong or is it indeed a requirement to use weak_ptr in these cyclic condition?

Upvotes: 4

Views: 960

Answers (3)

marcinj
marcinj

Reputation: 50026

Here:

node d = *foo();

you dereference shared_ptr, so d contains a copy of node which was created in foo in line:

shared_ptr<node> a = make_shared<node>();

this a will be destroyed just after node d = *foo();. This is because parent is only a weak_ptr inside node.

In regards to this, is there anyway to prevent it (other than not dereferencing it)?

Not dereferencing seems to be good aproach.

You could switch from weak_tr<node> parent; to shared_ptr<node> parent;. Other solution would be to keep a global shared_ptr<node> root; which would keep a reference to your a. But It depends on what your code really is going todo.

Upvotes: 2

Cheers and hth. - Alf
Cheers and hth. - Alf

Reputation: 145419

A weak_ptr expires when the last shared_ptr that refers to the object, is destroyed.

In your code that happens in the statement

node d = *foo();

Here foo() returns a shared_ptr, which is the last shared_ptr that refers to that object (the parent object of the two created by foo). And this shared_ptr is a temporary that's destroyed right there, after the derefencing. That reduces the reference count to 0 and the weak_ptr expires.

Since the shared_ptr was the last one, the object is destroyed, which causes its child object to also be destroyed. Thus the later code that delves into these objects has undefined behavior.

Since d contains a shared_ptr to the child node, the child node is not destroyed at this point, as noted by Miles Budnek in a comment.

Upvotes: 6

kfsone
kfsone

Reputation: 24269

node d = *foo();

foo returns a shared_ptr which is keeping the parent node allocated in foo alive.

You then copy the contents of that into d but do not store the shared_ptr, so that is destroyed at the end of the statement. There are now no shared_ptr instances referencing the node instance dynamically allocated in foo, so the weak pointer references to it are now expired.

The dereference isn't the problem: the problem is the failure to capture the shared_ptr being returned.

Upvotes: 3

Related Questions