It is important to understand that what a compiler must do and what a compiler can do are as different things as what a piece of code formally means and what it really means.
Take a much smaller and easier example, right from the code snippet you posted: i++
.
What your code formally means is: Make a copy of i
, then increment i
, and have the expression have the value of the copy that you made previously.
What your code really means is: Increment i
and screw the rest (because I'm not using the result).
What the compiler must do is implement exactly the (externally visible) semantics of the "what your code formally means" part. None more and none less.
What the compiler can do is incrementi
and screw the rest (because nobody can tell the difference).
Generally, a compiler may make any changes it likes based on the "as if" rule, which, sloppily means that as long as the externally observeable behaviour stays the same, nobody cares. Move semantics are a means to bend the rules a bit further, both implicitly and explicitly.
When you move an object rather than copying it, you are basically cheating. However, the difference between "cheating" and "magic" is whether you're being caught or not (just like the difference between "genius" and "madness" is defined only by success).
You're abusing an object that is going to be destroyed the next instant anyway, claiming it's a copy when it is not. However, nobody will notice because the moved-from object is a rvalue, i.e. it has no name (and thus isn't otherwise accessible) and is destroyed right away, too. On the other hand, the new object that you present to the world which isn't new at all, but it is nevertheless a perfectly good object by all means (the original one!). That's the whole trick.
About your second question: Your doubleValues
function takes a std::vector
by constant reference. That's not a copy, it's as cheap as a pointer. However, then you copy all the values into a new object new_values
.
Then, you return that object by value, which means you formally make a second copy, and finally you invoke the assignment operator of std::vector
, which, actually, is even a third copy.
Formally, that is. An optimizing compiler may, and hopefully will, optimize that away (NRVO).
Used to be compilers would only do that with unnamed temporaries, but that was like... 10 years ago. Nowadays, named return value optimization usually/often "just works", luckily.
Under some conditions (although not in your example), the compiler is even required to perform copy elision per the latest standard.