Yida Zhang
Yida Zhang

Reputation: 13

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

Upvotes: 0

Views: 255

Answers (3)

Laurent LA RIZZA
Laurent LA RIZZA

Reputation: 2965

Let’s go through these one by one:

auto i = f(1); // (1)

This creates a local variable i (not a reference) initialized with the return value of f(1). This is OK. The lifetime of i is until the end of the block.

p && j = f(2); // (2)

This initializes the reference j with a reference to the object returned by f(2), and this extends the lifetime of the value returned by f(2), so this is OK. The lifetime of j is until the end of the block.

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

This initializes the reference with a reference to the object returned by move, no lifetime extension occurs since the value returned by move is a reference to the temporary returned by f(2) (and not a temporary object), but the temporary returned by f(2) dies as soon as k is initialized, since its lifetime is not extended (it’s not assigned to a reference variable), and k ends up being a dangling reference.

auto l = move(f(4)); // (4)

Same as (1), the move is useless. The lifetime of l is until the end of the block.

p ptr = make_shared<A>(5); // (5)

This is a local variable initialization. The lifetime of ptr is until the end of the block.

auto && a = move(ptr);

a is a reference to ptr. This does not change the lifetime of ptr.

Upvotes: 2

Miles Budnek
Miles Budnek

Reputation: 30494

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.

Upvotes: 2

Zan Lynx
Zan Lynx

Reputation: 54325

Because by using std::move you've turned the return value into a reference. And not a const reference, so it's a temporary.

So no copy takes place and as soon as the line ends, the return value is destroyed.

Upvotes: 1

Related Questions