54

Is RVO (Return Value Optimization) guaranteed or applicable for all objects and situations in C++ compilers (specially GCC)?

If answer is "no", what are the conditions of this optimization for a class/object? How can I force or encourage the compiler to do a RVO on a specific returned value?

masoud
  • 55,379
  • 16
  • 141
  • 208

5 Answers5

52

Return Value Optimization can always be applied, what cannot be universally applied is Named Return Value Optimization. Basically, for the optimization to take place, the compiler must know what object is going to be returned at the place where the object is constructed.

In the case of RVO (where a temporary is returned) that condition is trivially met: the object is constructed in the return statement, and well, it is returned.

In the case of NRVO, you would have to analyze the code to understand whether the compiler can know or not that information. If the analysis of the function is simple, chances are that the compiler will optimize it (single return statement that does not contain a conditional, for example; multiple return statements of the same object; multiple return statements like T f() { if (condition) { T r; return r; } else { T r2; return r2; } } where the compiler knows that r or r2 will be returned...)

Note that you can only assume the optimization in simple cases, specifically, the example in wikipedia could actually be optimized by a smart enough compiler:

std::string f( bool x ) {
   std::string a("a"), b("b");
   if ( x ) return a; 
   else return b;
}

Can be rewritten by the compiler into:

std::string f( bool x ) {
   if ( x ) {
      std::string a("a"), b("b");
      return a;
   } else {
      std::string a("a"), b("b");
      return b;
   }
}

And the compiler can know at this time that in the first branch a is to be constructed in place of the returned object, and in the second branch the same applies to b. But I would not count on that. If the code is complex, assume that the compiler will not be able to produce the optimization.

EDIT: There is one case that I have not mentioned explicitly, the compiler is not allowed (in most cases even if it was allowed, it could not possibly do it) to optimize away the copy from an argument to the function to the return statement:

T f( T value ) { return value; } // Cannot be optimized away --but can be converted into
                                 // a move operation if available.
David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
  • Best correct the wikipedia article and the references that it cites, including a Hinnant piece. – Lightness Races in Orbit Sep 29 '11 at 11:54
  • The wikipedia article is not *wrong*, the comment in the example explicitly states *RVO might not be applied*. I was just pointing out that depending on the compiler even non-trivial copies might be avoided. – David Rodríguez - dribeas Sep 29 '11 at 14:00
  • 3
    Note that RVO/NRVO is only applicable when returning a *temporary object*, i.e. an object that would normally be destructed after the copy. For example, when returning a member variable from a member function, a copy must occur. This may seem obvious to some, but I have had to explain this to multiple people as they just learn about RVO. – JDiMatteo Jan 14 '15 at 17:08
  • 1
    @DavidRodríguez-dribeas Could you explain your final example? Isn't `value` a temporary by way of being passed by value, and therefore perfectly eligible for RVO? – Owen May 09 '15 at 18:15
  • 1
    @Owen: `value` won't outlive the function, but it is not a temporary (not that it matters). The language does not have a provision for that copy to be elided, which is the reason for the note. As of why, copy elision is done by creating the copy directly over the memory that the copy would have, then no copy is necessary. Common ABIs have the caller reserve the space for the returned object, and it is also the caller that copies the argument. But in a language with separate compilation, the caller does not know if the function will return that argument [...] – David Rodríguez - dribeas May 09 '15 at 22:59
  • [...] Even if it knew, the ABI might not allow he elision as it might determine a fixed location for the argument. – David Rodríguez - dribeas May 09 '15 at 23:00
5

Is RVO (Return Value Optimization) guaranteed for all objects in gcc compilers?

No optimisation is ever guaranteed (though RVO is fairly dependable, there do exist some cases that throw it off).

If answer is "no", what is the conditions of this optimization for a class/object?

An implementation detail that's quite deliberately abstracted from you.

Neither know nor care about this, please.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
  • 7
    Your last point is a bit of a Javaish comment.. ;) There's no harm is knowing, it may be implementation specific, but if so inclined - why not? However, as for caring, only if profiling highlights it... – Nim Sep 29 '11 at 11:00
  • 1
    @Nim: It's "wrong" to want to know about which optimisations are being performed. OK, fair enough, if you're really tightening up your program for some mission-critical system then profiling _might_ lead you down the path of dark arts into specifically what your compiler is doing, but this should not be the default approach. – Lightness Races in Orbit Sep 29 '11 at 11:03
  • @Nim: Because those conditions can change at any time. They're not actually dependable. – Puppy Sep 29 '11 at 11:12
  • @DeadMG, of course if you go down that route, then this is a pitfall that you have to be aware of... I did say "so inclined", and only particular types of folk are "so inclined"... ;) – Nim Sep 29 '11 at 11:14
  • @Nim: Where "so inclined" is "so misguided" :) – Lightness Races in Orbit Sep 29 '11 at 11:17
  • 2
    RVO **can** always be applied, NRVO is the one that cannot be applied at times. – David Rodríguez - dribeas Sep 29 '11 at 11:33
  • @TomalakGeret'kal :) I don't see why Hinnant should be involved here, and it was Walter Bright the first implementer of NRVO who made a distinction of NRVO vs. RVO (returning a local variable vs. returning a temporary) --it is all there in that linked article. – David Rodríguez - dribeas Sep 29 '11 at 13:58
  • @David: Hinnant is involved because it's his work that's cited in the Wikipedia article that you claim is factually inaccurate. – Lightness Races in Orbit Sep 29 '11 at 16:20
  • @TomalakGeret'kal: I still fail to see it... Hinnant is not mentioned in the history of changes, so I will assume that he has not written the wikipedia article. The only mention is N1377 that deals with the same concepts (whether a copy in a return statement can be optimized away, and offering move semantics to help when it cannot), in the proposal Hinnant talks about NRVO (not RVO), I have read the proposal and I don't see any contradiction at all from what I mentioned. (He does even mention that NRVO can be applied to the example in wikipedia, even if it is hard to implement!) – David Rodríguez - dribeas Sep 29 '11 at 16:31
  • @David: I didn't say that he wrote the Wikipedia article. I pointed out that the article cites him as a source of information. [edit: ah, you found it] – Lightness Races in Orbit Sep 29 '11 at 16:37
  • @TomalakGeret'kal: I still don't follow, what were you trying to tell me, that Hinnant had written the same before? I thought that the *Best tell Hinnant* comment was sarcastic and you were implicitly stating that Hinnant disagreed with what I said (as in *Best correct the wikipedia article, and the references including a Hinnant piece*... well, I don't have anything to correct, we agree. – David Rodríguez - dribeas Sep 29 '11 at 17:09
  • 1
    For a lot of (non-mission-critical) applications, "should be optimized in most of the cases" is good enough, hence the reasoning behind knowing when it's generally performed. If by slightly modifying a program you get considerable performance boost in most of the cases, why not? – Piyush Soni Aug 16 '16 at 05:06
3

Move semantics (new feature of C++11) is a solution to your problem, which allows you to use Type(Type &&r); (the move constructor) explicitly, instead of Type(const Type &r) (the copy constructor).

For example:

class String {
  public:    
    char *buffer;

    String(const char *s) { 
      int n = strlen(s) + 1;
      buffer = new char[n];
      memcpy(buffer, s, n);
    }

    ~String() { delete [] buffer; }
    
    String(const String &r) { 
      // traditional copy ...
    }

    String(String &&r) {
      buffer = r.buffer; // O(1), No copying, saves time.
      r.buffer = 0;
    }
};

String hello(bool world) {
  if (world) {
    return String("Hello, world.");
  } else {
    return String("Hello.");
  }
}

int main() {
  String foo = hello();
  std::cout <<foo.buffer <<std::endl;
}

And this will not trigger the copy constructor.

RnMss
  • 3,696
  • 3
  • 28
  • 37
  • -1 because if you use return std::move(temp_object) the object might get out of scope (easy example, T t; return std::move(t);) and you will be left with an invalid reference. As you use String in this example it MIGHT work because Strings are kept in a pool and may not be deleted even if out of scope but don't trust that in general. – WorldSEnder May 21 '14 at 16:34
  • @WorldSEnder I don't see invalid reference here. `return`-ing an object would make it construct a new object (by either copying or moving), not a reference of the temp object. What you said would only happen when the return type is a reference or pointer. – RnMss May 22 '14 at 02:48
  • You're right if you don't return String&&. Thanks, I thought it was different. – WorldSEnder May 22 '14 at 10:03
  • For anyone still stumbling upon this answer, note that you should *not* use `std::move` in a return statement. Doing so has no benefit and may actually [prevent the compiler](https://stackoverflow.com/questions/4986673/c11-rvalues-and-move-semantics-confusion-return-statement) from taking advantage of RVO. Ironically, this answers the question "Is RVO applicable for all objects?" by producing a case where RVO is not allowed. – Brian61354270 Nov 20 '20 at 14:13
  • @Brian I edited the answer a little bit. Well ..., it was years ago and I didn't understand about mechanics like pr-values, x-values. Now we have C++17 (and C++20 soon), where copy elision is fully standardized, I possibly won't mention move semantics. However, this answer was meant to deal with the case where RVO can not be guaranteed. – RnMss Nov 21 '20 at 12:04
  • @Brian "you should not use std::move in a return statement", this is not correct, if the value is an r-value reference. e.g. `T foo(T&& x) { return x; }` this will perform a copy construction not a move construction. To avoid copy construction, you have to write `return std::move(x);` instead. So if you don't want a copy but are not sure something is a copy elision, use move, and it won't hurt anything. – RnMss Nov 21 '20 at 12:24
3

To Jesper: if the object to be constructed is big, avoiding the copy might be necessary (or at the very least highly desirable).

If RVO happens, the copy is avoided and you need not write any more lines of code.

If it doesn't, you'll have to do it manually, writing extra scaffolding yourself. And this will probably involve designating a buffer in advance, forcing you to write a constructor for this empty (probably invalid, you can see how this is not clean) object and a method to ‘construct’ this invalid object.

So ‘It can reduce my lines of code if it's guaranteed. Isn't it?’ does not mean that Masoud is a moron. Unfortunately for him however, RVO is not guaranteed. You have to test if it happens and if it doesn't, write the scaffolding and pollute your design. It can't be herped.

0

I don't have a yes or no answer, but you say that you can write fewer lines of code if the optimization you're looking for is guaranteed.

If you write the code you need to write, the program will always work, and if the optimization is there, it will work faster. If there is indeed a case where the optimization "fills in the blank" in the logic rather than the mechanics of the code and makes it work, or outright changes the logic, that seems like a bug that I'd want fixed rather than an implementation detail I'd want to rely on or exploit.

Jesper
  • 7,477
  • 4
  • 40
  • 57
  • I don't mind downvotes, but please explain why. I don't see what's wrong with saying "please don't make your code rely on optimizations". – Jesper Sep 29 '11 at 11:18
  • I didn't downvote you, but I'm not sure that this really answers the question. It sort of strafes it slightly. :) – Lightness Races in Orbit Sep 29 '11 at 11:27
  • That's fair enough. Sometimes the answer really is "please god don't do this" although I think this question is far from that. – Jesper Sep 29 '11 at 13:32
  • 1
    @MasoudM. Call me crazy, but I still contend that code that doesn't work correctly if a certain optimization is missing is broken. The reason optimizations are made are to speed up the logic, not to "fix" it. This is different from knowing where optimizations occur and simply writing your code to target them - even if that optimization went missing or was disabled, your code would still work. – Jesper Jan 09 '13 at 08:57