Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does this work with Return Value Optimization?

Tags:

c++

vector<int> f(const vector<int>& v)
{
    vector<int> ret(v.size());
    fill(ret.begin(), ret.end(), 123);
    copy(v.begin(), v.end(), ret.begin());
    return ret;
}

int main()
{
    vector<int> v(10);
    v = f(v);
}

If return value optimization is applied to f, then the local variable ret shares the same address as v in main. But if this is true, filling ret will trash the data in v before the copy. The code is correct without RVO and optimizations shouldn't break behavior.

Is this safe or do I not understand RVO correctly?

like image 627
Neil Kirk Avatar asked Sep 06 '13 16:09

Neil Kirk


People also ask

Is return value optimization guaranteed?

Compilers often perform Named Return Value Optimization (NRVO) in such cases, but it is not guaranteed.

Does C have return value optimization?

> Note also that C doesn't have return-value-optimization, hence all your struct-returning functions will cause a call to memcpy (won't happen when compiled in C++ mode of course).

What is RVO and NRVO?

One form of copy elision is known as "named return value optimization" - a.k.a NRVO. Another one is known as "return value optimization" - a.k.a RVO. There are also other forms of copy elision which don't involve returned values.

What does FNO elide constructors do?

If “-fno-elide-constructors” option is used, first default constructor is called to create a temporary object, then copy constructor is called to copy the temporary object to ob.


2 Answers

What happens is this:

On the caller's side, a return slot is provided which can hold the result, that means that the caller provides the memory for the variable of type std::vector<int>. It expects the called method to construct the value and is itself responsible for calling the destructor when the result is no longer used and freeing the memory (if necessary, it probably just lives on the stack).

The called function (which may live in a different translation unit!) would, without the NRVO, so this:

  • Provide a memory slot for ret.
  • Construct a local variable ret in this memory slot.
  • Do stuff...
  • Copy-construct the return value in the provided memory slot by copying ret.
  • Call ret's destructor.

Now, with the NRVO, the decision to optimize this can be done in the called function's translation unit. It transforms the above into:

  • Construct ret in the memory of the method's return slot.
  • Do stuff...

No need to do anything else as the memory is owned and the destructor is called by the caller and because the optimization is transparent for the caller :)

This, of course, can't eliminate the assignment into v in your example. If you store the result in a different variable, e.g.

std::vector<int> w = f(v);

the NRVO will construct ret directly into w's memory (as this will be passed in as the return slot to f).

like image 72
Daniel Frey Avatar answered Sep 16 '22 20:09

Daniel Frey


The code is correct, your understanding of RVO is not - specifically this:

the local variable ret shares the same address as v

It doesn't share the address of the variable you assign it to. Technically, when you return an automatic local variable, it will be copied to a temporary. RVO skips that part.

It goes like this:

         create variable `ret`
                   |
 copy ret to a temporary before returning
                   |
       assign the temporary to v

NRVO (in this case) would skip the second part.

Compilers are smart though, so don't be surprised if it just optimizes the whole thing, since there's no observable behavior at all.

like image 31
Luchian Grigore Avatar answered Sep 17 '22 20:09

Luchian Grigore