How does the compiler determine when is it safe to RVO?

488 views Asked by At

How does the compiler determine when it is safe to RVO? And no, I don't mean the rvalue, but the lvalue - if I understand correctly RVO works by "forwarding" the target address to the method, so it returns into the target address instead of the address of a temporary and then copy/assign to the target.

But how does the compiler know it is safe to perform a RVO? What if the lvalue already has some data in it, including dynamically allocated resource? A RVO in such a case may result in a leaked resource. Mayhaps there are some rules which specify whether it is applicable to perform the optimization or stick to using copy or assignment?

2

There are 2 answers

18
Mike Seymour On BEST ANSWER

RVO can only initialise a new object, not reassign an existing object. So in this case:

Thing thing = make_thing();

the address of thing is passed to the function, which initialises it in place.

In this case:

thing = make_thing();

RVO (in general) creates a temporary, which is then assigned to thing. There will be no leaks or similar issues, as long as the type is correctly assignable. Since it's a temporary rvalue, it can be moved from, which might be more efficient than copying. If the type is trivially assignable, then this assignment could also be elided - the compiler will know whether this is the case when it chooses how to call the function.

3
Joseph Mansfield On

Return value optimization is a particular case of copy elision. It may occur in the following situation as described by the standard:

in a return statement in a function with a class return type, when the expression is the name of a non-volatile automatic object (other than a function or catch-clause parameter) with the same cv- unqualified type as the function return type, the copy/move operation can be omitted by constructing the automatic object directly into the function’s return value

There is no reason this should result in a memory leak. If the class performs some dynamic allocation in its constructor, this will happen when the object is constructed directly into the function's return value.


In response to your comment (where foo1 and foo2 both construct T objects and return them):

T a = foo1();
a = foo2();

We're not only looking at RVO here, but another kind of copy elision that occurs when attempting to construct an object from a temporary.

In the first line, two copies/moves can be elided:

  1. Returning the constructed object from foo1
  2. Copying the returned object into a

That is, the object constructed in foo1 can be directly created in the location of a. If the constructor dynamically allocates some object, that will only be done once, for the a object.

In the second line, a single copy/move can be elided - only the return from the function. So the object that foo2 constructs will be created directly in the return value of the function, then it will be copy/move assigned into a. Copy/move assignments aren't elided.

It is then up to the copy/move assignment operator to ensure that the original allocated resource is discarded safely and the only remaining resource is the one that was created inside foo2.