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

5
  • 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. Commented Jul 16, 2022 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) Commented Jul 16, 2022 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 Commented Jul 16, 2022 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. Commented Jul 16, 2022 at 21:15
  • Some good viewing: A presentation by Howard Hinnant on move semantics. Pretty much covers all of the bases down to the whys. Commented Jul 17, 2022 at 5:15

2 Answers 2

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.

Sign up to request clarification or add additional context in comments.

4 Comments

"...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.
The language I used is similar to what's in 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.
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. 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.
@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.
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.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.