简体   繁体   中英

Why doesn't std::move() of unique_ptr from list<unique_ptr> really move it?

using Ptr = std::unique_ptr<int>;

Ptr f(bool arg) {
  std::list<Ptr> list;
  Ptr ptr(new int(1));
  list.push_back(std::move(ptr));

  if (arg) {
    Ptr&& obj1 = std::move(list.front());
    // Here |obj1| and |list.front()| still point to the same location!
    list.pop_front();
    return std::move(obj1);
  }
  else {
    Ptr obj2 = std::move(list.front());
    list.pop_front();
    return obj2;
  }
};

Ptr&& ptr1 = f(true);   // |ptr1| is empty.
Ptr&& ptr2 = f(false);  // |ptr2| is fine.

The full source is here .

I don't understand — why do obj1 and list.front() still point to the same location after std::move() is called?

You have a reference to the std::unique_ptr inside list .

Ptr&& obj1 = std::move(list.front());
// ^^ THIS

And so when you do

list.pop_front();

The unique pointer that's in list gets destroyed, and you are left with a dangling reference to some object that is already destroyed, which is illegally used by returning it from the function.

UPDATE: std::move there doesn't actually move the std::unique_ptr . If we see how std::move is defined , we see that it only returns an r-value reference of the expression being moved. It becomes interesting when you test for it:

Ptr&& obj1 = std::move(list.front());
assert(obj1.get() == list.front().get());

Your else clause does seems fine though, and you should use that pattern for the rest of your code.

Ptr obj2 = std::move(list.front());
list.pop_front();  // OK; destroys the moved-from unique_ptr
return obj2;       // OK; obj2 points to something valid and is not a dangling reference

@nosid has summed-up what I want this answer to mean .

Without move-semantics std::unique_ptr is less useful. With move-semantics it allows transfer of ownership to another object .

With list.push_back(std::move(ptr)); you're transferring the ownership of the data to a new element and leaving ptr in a nullptr state ( read here ).

After that, if arg is true, since list.front() returns a reference to the first element in the container , std::move takes an r-value out of it and feeds it to the r-value reference obj1. Notice that you're not transferring ownership to another object since you're only asking for a r-value reference to the data. An r-value reference is, in the end, a reference to a value .

In the specific case above, regarding the cout statement, it is equivalent to getting just a simple reference to the object

Ptr& obj1 = list.front();

Now you have both the element into the list and the obj1 r-value reference "pointing" to the same data and modifying one will cause modifying both

Ptr&& obj1 = std::move(list.front());
std::cout << obj1.get() << std::endl << list.front().get() << std::endl; // same address
obj1.reset(new int(2));
std::cout << obj1.get() << std::endl << list.front().get() << std::endl; // same other address

if you were, as in the false case, to do

Ptr obj1 = std::move(list.front());

then you would have had the ownership (and thus the nullptr-ify of the list object) transfer to obj1.

If you followed the above reasoning, you can also realize that the line

list.pop_front();

destroyes the unique_ptr object and leaves the r-value reference into an undefined state. You shouldn't really be returning it (and using it).

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