5
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?

Greg
  • 9,068
  • 6
  • 49
  • 91
abyss.7
  • 13,882
  • 11
  • 56
  • 100
  • 5
    Why do you use so many rvalue-references? If you use `Ptr obj1 =` instead of `Ptr&& obj1 =`, everything should be fine. – nosid Mar 01 '14 at 09:05
  • @nosid I have a fuzzy explanation. I'm trying to use the `std::experimental::optional` from libc++ - instead of `Ptr`. My `optional` contains pairs of `unique_ptr`s and move-only objects (i.e. without default ctor). The variant `Ptr obj2 =` doesn't even compile - but the `Ptr&& obj1 =` does. And then I was surprised about the situation that I explained in the question. Of course, I should examine the problem with `optional` itself, but it's easier to make clear the easiest things first. – abyss.7 Mar 01 '14 at 09:50
  • 1
    Think about the expression `std::move(list.front())` in isolation. If `std::move` did actually move its argument (hint: it does not), where exactly would the object be moved *to*? – fredoverflow Mar 01 '14 at 10:33
  • @FredOverflow My guess - it should be moved to the temporary object, like `Ptr()`, that gets destroyed when leaves the scope - in normal situation. But because of rvalue-reference - it should leave a little bit longer. – abyss.7 Mar 01 '14 at 10:37
  • Actually, your guess doesn't sound unreasonable, but that's just not how it works. – fredoverflow Mar 01 '14 at 10:38
  • std::move doesn't move; it's misnamed, as acknowledged by Stroustrup. Details and references here: http://stackoverflow.com/questions/21358432/why-is-stdmove-named-stdmove (and maybe this could be considered a dup of that question). – Don Hatch Nov 04 '16 at 01:34

2 Answers2

4

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.

Community
  • 1
  • 1
Mark Garcia
  • 17,424
  • 4
  • 58
  • 94
1

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

Marco A.
  • 43,032
  • 26
  • 132
  • 246