1

I was reading the chapter 13 of C++ Primer when I read :

"It is essential to realize that the call to move promises that we do not intend to use [an rvalue] again except to assign to it or to destroy it. After a call to move, we cannot make any assumptions about the value of the moved-from object."

"We can destroy a moved-from object and can assign a new value to it, but we cannot use the value of a moved-from object."

So I wrote this code to see if I can't actually use a rvalue twice :

#include <iostream>

int main(int argc, const char * argv[]) {

    int&& rvalue = 42;
    
    int lvalue1 = std::move(rvalue);
    
    std::cout << "lvalue1 = " << lvalue1 << std::endl;
    
    int lvalue2 = std::move(rvalue);
    
    std::cout << "lvalue2 = " << lvalue2 << std::endl;
    
    return 0;
}

But according to the output of this code, C++ Primer is wrong and I can use the value of a moved-from object several times (I built with the C++11 standard) :

lvalue1 = 42
lvalue2 = 42

I don't understand...

DigitalRomance
  • 305
  • 2
  • 7
  • Think about what an `int` is. There isn't anything to move from it. It would cost more to copy over the 42 and then erase the 42 than it would to just copy the 42 and leave the 42 in place. In the general sense when you move from an object it is placed in a safe but undefined state. And in this case leaving 42 is perfectly safe. But you can't count on this behaviour. Try this with something more complicated like `std::string` and you'll get very different results. – user4581301 Jul 16 '22 at 20:40
  • You mean when std::move is used on something simple like an int the compiler optimizes the code and therefore std::move isn't actually called at the end ? (maybe it's impossible to know...) In each case you were right, I did the same experience with a std::string and the result is as expected : lvalue1 = "Some string" lvalue2 = (blank) – DigitalRomance Jul 16 '22 at 21:02
  • 6
    @DigitalRomance `std::move` is just a cast, it only enables moving if the type supports it. `std::move` expresses the concept that - this thing is now temporary and I don't care what you (the compiler) do with it. cppreference has a nice quote "_...std::move is used to __indicate__ that an object __may be__ "moved from"..."_ [std::move](https://en.cppreference.com/w/cpp/utility/move) – Richard Critten Jul 16 '22 at 21:03
  • 2
    Re: "After a call to move, we cannot make any assumptions about the value of the moved-from object." -- that's simply not true. First, calling `move` doesn't do anything to the object. But if the object is actually moved from, if the object is a type defined by the standard library you can call any function that has no preconditions. If it's an object that's not from the standard library you can do anything that its type allows; read its documentation. – Pete Becker Jul 16 '22 at 21:15
  • Some good viewing: [A presentation by Howard Hinnant on move semantics](https://www.youtube.com/watch?v=vLinb2fgkHk). Pretty much covers all of the bases down to the whys. – user4581301 Jul 17 '22 at 05:15

2 Answers2

2

This experiment ...

So I wrote this code to see if I can't actually use a rvalue twice :

... doesn't prove anything. It might also be the case that your particular compiler doesn't complain when you, say, double-delete a pointer. That doesn't make this good practice or even language-defined behavior.

After a std::move the moved-from variable is in a valid but unspecified state. Being valid means you can reassign to it, but unspecified means that until that reassignment happens the variable's value is not guaranteed to be anything in particular. Maybe the variable appears to maintain its value after a std::move; that's consistent with an unspecified state. The guidance in your text--"make [no] assumptions about the value of the moved-from object"--is accurate.

  • _"...After a `std::move` the moved-from variable is in a valid but unspecified state ..."_ this is incorrect: nothing has changed except the expression is now an `xvalue`, it all depends on what happens to the `xvalue` in the complete expression. Saying _"... after `std::move` ..."_ gives the wrong impression of what `std::move` actually does. – Richard Critten Jul 17 '22 at 07:47
  • The language I used is similar to what's in https://en.cppreference.com/w/cpp/utility/move: "all standard library objects that have been moved from are placed in a 'valid but unspecified state'". Elsewhere (with emphasis added): "The functions that accept rvalue reference parameters [...] **have the option**, but aren't required, to move any resources held by the argument.". The argument to `std::move` would be declared `const` if the function wasn't allowed to change it under any circumstances. – Dimitrije Kostic Jul 17 '22 at 16:45
  • `std::move` - _"...It is exactly equivalent to a static_cast to an rvalue reference type...."_ ie it's just a cast and does nothing to change it's augment's value just its value-category. https://en.cppreference.com/w/cpp/utility/move . It's the expression that uses the result of `std::move` that is allow to move-from the value, because it now knows it's a rvalue. – Richard Critten Jul 17 '22 at 16:48
  • @RichardCritten Try the following experiment. Create `std::string` variables `&&s = "AAA"` and `move_s = ""`. Then run `move_s = std::move(s)` and print the two variables out. On my machine I see that `move_s == "AAA"`. That's clear evidence that `std::move` changed `move_s`'s value. All `move` is guaranteed to do is indicate whether the argument can be moved from but, if possible, it will try to move the argument. (It would be pretty odd if a function named `move` never tried to move anything.) Because it might not be able to do the move, the argument is left in an unspecified state. – Dimitrije Kostic Jul 23 '22 at 04:56
0

The wording in the primer is very carefully chosen:

but we cannot use the value of a moved-from object.

"Use" here is ...err, used, in a very general sense. It means, basically: if you have any carefully-laid plans to use it, and expect the object to have anything particilar in it after the move, you are going to be very, very disappointed. "Cannot use" really means here: well, what use could possibly exist for any object whose value is unspecified? What good is an object whose value is unknown? That doesn't sound like a very useful object, doesn't it?

But a moved-from object is not really some kind of an untouchable. It's still a valid object. It still exists. If the moved-from object has a destructor the destructor will still get invoke when the moved-from object goes out of scope and gets destroyed. However, immediately after a move, it's "value" is unspecified.

The C++ standard describes move semantics thusly:

Moved-from state of library types [lib.types.movedfrom]

Objects of types defined in the C++ standard library may be moved from. Move operations may be explicitly specified or implicitly generated. Unless otherwise specified, such moved-from objects shall be placed in a valid but unspecified state.

Emphasis mine. The above technically refers to the objects in the C++ library only, which technically encompasses the native types, too. "Valid but unspecified state" is the key. In your example, the first move operation left the original valid in some "valid but unspecified state". And the key word there is "valid". So, it is not prohibited to move the object again.

In this case, your result happened to produce another 42. But the moved-from value is "unspecified". Someone else might get a different value. Or you may get a random value each time you run the program. Of course, practically, that's not going to happen, but that's what the C++ standard allows to happen.

Sam Varshavchik
  • 114,536
  • 5
  • 94
  • 148