Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 888
abyss.7 Avatar asked Mar 01 '14 09:03

abyss.7


People also ask

Can you move a unique_ptr?

A unique_ptr can only be moved. This means that the ownership of the memory resource is transferred to another unique_ptr and the original unique_ptr no longer owns it. We recommend that you restrict an object to one owner, because multiple ownership adds complexity to the program logic.

What happens when unique_ptr goes out of scope?

std::unique_ptr is a smart pointer that owns and manages another object through a pointer and disposes of that object when the unique_ptr goes out of scope. The object is disposed of, using the associated deleter when either of the following happens: the managing unique_ptr object is destroyed.

Is unique_ptr null after move?

Yes, you can compare it to nullptr after the move and it is guaranteed to compare equal. This is clearly true after calling release().

Can unique_ptr be copied?

unique_ptr is not copyable, it is only moveable. This will directly affect Test, which is, in your second, example also only moveable and not copyable. In fact, it is good that you use unique_ptr which protects you from a big mistake.


2 Answers

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.

like image 123
Mark Garcia Avatar answered Oct 11 '22 17:10

Mark Garcia


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).

like image 24
Marco A. Avatar answered Oct 11 '22 17:10

Marco A.