3

I am reading about move semantics and this code is given:

#include <iostream>

using namespace std;

vector<int> doubleValues (const vector<int>& v)
{
    vector<int> new_values;
    new_values.reserve(v.size());
    for (auto itr = v.begin(), end_itr = v.end(); itr != end_itr; ++itr )
    {
        new_values.push_back( 2 * *itr );
    }
    return new_values;
}

int main()
{
    vector<int> v;
    for ( int i = 0; i < 100; i++ )
    {
        v.push_back( i );
    }
    v = doubleValues( v );
}

So the author states that after the return of doubleValues, there could be up to two copies happening

one into a temporary object to be returned, and a second when the vector assignment operator runs on the line v = doubleValues( v );

He also states that the first copy may be optimized away.

Here is what I do not get:

  • What does he mean when saying "one into a temporary object to be returned"? Isn't it a temporary object that is returned by the function? If so, I do not see why anything should be copied to another temporary object.

  • He states that this temporary object could be optimized away. How would a temporary object be optimized away, like what is considered an optimization?

  • @Destructor thank you. Not that I haven't skimmed the Q&A but does that answer **both** of my questions or is it just the last one? The thing is that currently I do not see an answer to the first one. –  Jul 06 '17 at 16:43

2 Answers2

2

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.

Damon
  • 67,688
  • 20
  • 135
  • 185
  • Wow thank you! But as I asked geza, "So does this mean that an empty temporary object is created whereafter the content of new_values is being copied into that temporary"? "Then, you return that object by value, which means you formally make a second copy" So what you are saying is that a new object is created and then the return value from the function is copied to the temporary? Where can I read more about this process(the thing where returned values create temporary objects, not sure if it has a name though)? –  Jul 06 '17 at 19:47
  • 2
    @FacPam: Yes, in theory that is just what must happen (not in practice, hopefully). Think about it: `new_values` has a automatic storage, i.e. a lifetime limited by the function, which means its members are on the stack (not the memory that std::vector allocates for the elements within, but the pointer to it, the end pointer, etc.). The information will be "gone" as soon as the function returns since the stack frame goes away. Thus, the only way to keep (return) that information is by copying. Or, _cheating_, that is only copying the pointers, and stealing the buffer. Which is... moving. – Damon Jul 07 '17 at 07:48
  • Ahhh that was a great explanation, really! Thank you :) –  Jul 07 '17 at 09:54
  • One more thing though: Is this process called anything? Because if so, I would like to read more about this, if there is more to say about than what you kindly did. "process" = Where the compiler creates a new temporary object to hold the return value. –  Jul 07 '17 at 09:58
1

You need to understand that if a function returns a value by value, then it is returned via a temporary. It means, a non-optimizing compiler will:

  1. copy new_values into a temporary, at return
  2. copy from this temporary to v at the last line if main()

The standard permits a compiler to elide any of these copies, if it can. For example, it can pass a hidden reference parameter to v in doubleValues, and doubleValues can use this hidden reference instead of new_values. This way, no copy will be performed.

Note: a c++11 compilant compiler will use move instead of copy

geza
  • 28,403
  • 6
  • 61
  • 135
  • Ahh thanks! So does this mean that an empty temporary object is created whereafter the content of new_values is being copied into that temporary, and then all the local objects of the function doubleValues are destroyed? If so, where can I read about this process in-depth? Thanks again! –  Jul 06 '17 at 19:12
  • 1
    @FacPam: almost. Not an empty temporary. The temporary will be constructed with its copy constructor (in case of pre c++11). If you now understand the main principles of this, the best you can do is to read the relevant part of the standard, which is available online (only drafts are available, but they are good for this purpose) – geza Jul 06 '17 at 19:53
  • @FacPam: or maybe someone can recommend a good book perhaps – geza Jul 06 '17 at 19:55
  • Ahh okay... SO would it be right to say that function returns by value create a temporary object to hold to given value, so it is actually not directly returning the literal value? –  Jul 06 '17 at 21:06
  • 1
    @FacPam: for a non-optimizing compiler, yes. The value on the stack will be copied into a temporary, then the value on the stack gets destroyed. And I cannot stop mentioning that is is for pre c++11. For c++11, move is used instead, which is much-much cheaper in the case of `vector<>`. All major compilers implement c++11 now, and even most of them are c++14 compliant. – geza Jul 06 '17 at 21:23
  • @FacPam: And as I've looked into c++17, this will change. For the most common scenario (where the returned value has the same type as the function's return value), copy **must not** made, and even, a copy constructor can be non-existent. – geza Jul 06 '17 at 21:30
  • 1
    @FacPam: Look at the publicly available draft c++11 [standard](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3337.pdf). It is basically the same as the released, official standard. You can find information about this in 6.6.3 and 12.2. – geza Jul 07 '17 at 10:19
  • Wow you actually answered my comments 12 hours ago.. did not really see that. Thank you and thank you :) You have helped me a lot –  Jul 07 '17 at 10:23