Search code examples
c++referencemoveshared-ptr

Why would a std::move of std::shared_ptr casue destruction


I got some code look like this

struct A
{
    int i;
    A(int i) : i(i) {}
    ~A()
    {
        cout << "destroy " << i << endl;
    }
};
using p = shared_ptr<A>;
p f(int i)
{
    return make_shared<A>(i);
}
int main()
{
    auto i = f(1);
    cout << "a" << endl;
    p && j = f(2);
    cout << "a" << endl;
    p && k = move(f(3));
    cout << "a" << endl;
    auto l = move(f(4));
    cout << "a" << endl;
    p ptr = make_shared<A>(5);
    auto && a = move(ptr);
    cout << "a" << endl;
}

and the output is

a
a
destroy 3
a
a
a
destroy 5
destroy 4
destroy 2
destroy 1

I don't understand why move a function's return value to a rvalue reference cause destruction. But put it directly to a rvalue reference is ok.

The problem is actually found with std::get<> of a std::tuple. I have a function that return a tuple of two shared_ptr and use std::get<> to access the element. But I found that auto && i = get<0>(f()) will cause error but auto i = get<0>(f()) won't. Then I find a similar but simpler situation for std::move


Solution

  • p && j = f(2);
    

    Here, f returns a prvalue of type std::shared_ptr<A>. When you bind a reference to a prvalue it extends the lifetime of the prvalue to be that of the reference. That means that the object returned by f will have the same lifetime as j.

    p && k = move(f(3));
    

    Here, once again f returns a prvalue. std::move's parameter binds to that prvalue, extending its lifetime to the lifetime of the parameter. std::move does not return a prvalue though. It returns an xvalue. Lifetime extension does not apply to xvalues, so the object returned by f gets destroyed as soon as std::move's parameter's lifetime ends. That is, it gets destroyed when std::move returns. k then becomes a dangling reference.