How does the compiler optimize temporary objects?

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

#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 claims that after returning doubleValues

, up to two copies can occur

one to a temporary object that will be returned, and the second when the vector assignment operator iterates over the line v = doubleValues ​​(v);

He also claims that the first copy can be optimized.

This is what I am not getting :

  • What does he mean by "one to a temporary object to be returned"? Isn't this a temporary object returned by the function? If this is the case, I don't understand why something should be copied to another temporary object.

  • He argues that this temporary object could be optimized. How would one optimize a temporary object, for example, what counts as an optimization?

+3


source to share


2 answers


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

  • copy new_values

    to a temporary folder,return

  • copy this temporary value to v

    in the last line ifmain()



The standard allows the compiler to strip any of these copies, if possible. For example, it can pass a hidden reference parameter v

to doubleValues

, or it doubleValues

can use that hidden reference instead new_values

. Thus, the copy will fail.

Note: the C ++ 11 compiler compiler will use move instead of copy

+1


source


It is important to understand what the compiler should do and what the compiler can do in the same way as what a piece of code formally means and what it really means.

Take a much smaller and simpler example, directly from a published piece of code: i++

.

What your code means formally is : Make a copy i

, then increment i

, and the expression has the value of the copy you made earlier.
What does your code really mean : Incrementing i

and screwing the rest (because I am not using the result).

What the compiler has to do is exactly implement the (externally visible) "part of your code" semantics. Nothing more and nothing less.
What the compiler does, is an increment i

and twists the rest (because no one can tell the difference).

Typically, the compiler can make whatever changes it likes based on the "as if" rule, which usually means that as long as the externally observable behavior remains the same, nobody cares. Moving semantics is a means to bend rules a little further, both implicitly and explicitly.



When you move an object rather than copy it, you are basically cheating. However, the difference between "cheating" and "magic" is whether you catch or not (just as the difference between "genius" and "insanity" is determined only by success).
You are abusing the object in any case, which will be destroyed the next moment, requiring a copy of it when it is not. However, no one will notice, because the relocated object is an rvalue, meaning it has no name (and therefore not available to others) and is immediately destroyed. On the other hand, a new object that you present to the world that is not new at all, but is nevertheless a perfectly good object (original!). This is the whole trick.

About your second question: your function doubleValues

takes a constant std::vector

. It's not a copy, it's cheap as a pointer. However, then you will copy all the values ​​into a new object new_values

.
Then you return that object by value, which means that you are formally making a second copy, and finally, you call the assignment operator std::vector

, which is, in fact, even the third copy.

Formally, that is. An optimizing compiler can and hopefully will optimize this (NRVO).
Used for compilers will only do this with unnamed tempos, but it looked like ... 10 years ago. Nowadays, the named optimization of the return value usually / often "just works", fortunately.

Under some conditions (although not in your example) the compiler even has to do a copy of the elision according to the latest standard.

+3


source







All Articles