简体   繁体   中英

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

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 .

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.

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM