5

This question is in different aspect (also limited to gcc). My question is meant only for unnamed objects. Return Value Optimization is allowed to change the observable behavior of the resulting program. This seems to be mentioned in standard also.

However, this "allowed to" term is confusing. Does it mean that RVO is guaranteed to happen on every compiler. Due to RVO below code changes it's observable behavior:

#include<iostream>
int global = 0;
struct A {
  A(int *p) {}
  A(const A &obj) { ++ global; }
};

A foo () {  return A(0); }  // <--- RVO happens
int main () {
  A obj = foo(); 
  std::cout<<"global = "<<global<<"\n"; // prints 0 instead of 2
}

Is this program suppose to print global = 0 for all implementations irrespective of compiler optimizations and method size of foo ?

Community
  • 1
  • 1
iammilind
  • 68,093
  • 33
  • 169
  • 336

3 Answers3

4

According to the standard, the program can print 0, 1 or 2. The specific paragraph in C++11 is 12.8p31 that starts with:

When certain criteria are met, an implementation is allowed to omit the copy/move construction of a class object, even if the copy/move constructor and/or destructor for the object have side effects.

Note that both copy elisions are not an optimization that falls in the as-if rule (which requires the behavior of the program to be consistent with the behavior of the same program as-if no optimization had taken place). The standard explicitly allows the implementation to generate different observable behaviors, and it is up to you the programmer to have your program not depend on that (or accept all three possible outcomes).

Note 2: 1 is not mentioned in any of the answers, but it is a possible outcome. There are two potential copies taking place, from the local variable in the function to the returned object to the object in main, the compiler can elide none, one or the two copies generating all three possible outputs.

David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
2

Pedantically speaking its implementation defined. Modern compilers are intelligent enough to do such kind of optimization.

But there is no guarantee that the behavior would be exactly same across implementations. That's what implementation defined behavior is all about.

"allowed to" in this context means that 0 or 1 or 2 are standard conforming outputs.

Prasoon Saurav
  • 91,295
  • 49
  • 239
  • 345
  • IMO only `0` should be the observable behavior. It's doubtful if standard can allow both the outputs. – iammilind Dec 07 '11 at 05:59
  • Why not? The standard also **allows** size of primitive types to be different across implementations. For example sizeof(int) could output 2 or 4 depending upon the compiler, right? There are several other examples as well. – Prasoon Saurav Dec 07 '11 at 06:01
  • 1
    @iammilind: The standard explicitly allows 0, 1 and 2 as output of that program. This is not part of the *as-if* rule for optimization, as the language explicitly requires that programs should not depend on side effects of the copy constructor and allows compilers to freely omit the copies – David Rodríguez - dribeas Dec 07 '11 at 07:11
  • 4
    @Prasoon Saurav: "Pedantically speaking its implementation defined" No. Pedantically speaking it is `unspecified`. "Implementation defined" means that compiler implementation's documentation must explicitly define it which is not true. – Serge Dundich Dec 07 '11 at 08:06
2

It cannot be guaranteed. If you tried to write such a guarantee coherently, you would find it impossible to do so.

For example, consider this code:

std::string f() {
  std::string first("first");
  std::string second("second");
  return FunctionThatIsAlwaysFalse() ? first : second;
}

The function FunctionThatIsAlwaysFalse always returns false, but you can only tell that if you do inter-module optimizations. Should the standard require every single compiler to do inter-module optimization so that it can use RVO in this case? How would that work? Or should it prohibit any compiler from using RVO when inter-module optimizations are needed? How would that work? How can it stop compilers that are smart enough to see that RVO is possible from doing it and those that are not from not doing it?

Should the standard list every optimization compilers are required to support with RVO? And should it prohibit RVO in other cases? Wouldn't that kind of defeat the point of optimizing compilers?

And what about the cases where the compiler believes RVO will reduce performance? Should the compiler be required to do an optimization it believes is bad? For example:

if(FunctionCompilerKnowsHasNoSideEffectsAndThinksMostlyReturnsFalse())
  return F(3); // Here RVO is a pessimization
else
{
 Foo j=F(3);
 return Foo(j);
}

Here, if the compiler is not required to do RTO, if can avoid the if and the function call, since without RTO, the code is the same in both halves. Should you force the compiler to do an optimization it thinks makes things worse? Why?

There's really no way to make such a guarantee work.

David Schwartz
  • 179,497
  • 17
  • 214
  • 278
  • 2
    Your answer is good in its way, however I had explicitly mentioned the RVO relation with **unnamed objects**. – iammilind Dec 07 '11 at 05:57
  • That was just an example of why RVO cannot be guaranteed. The principle is the same -- you'd have to specify that exact same set of optimizations for every compiler, defeating the point of permitting an optimization. (Consider a case where RVO may actually reduce performance, for example, where the RVO is an a branch the compiler predicts will almost never be taken.) – David Schwartz Dec 07 '11 at 05:58
  • 3
    @DavidSchwartz: While this is an example, in your particular case if you replace the variables by the constructions in place (i.e. `return condition() ? std::string("first") : std::string("second");`) all compilers I know of will avoid the copy. The fact that the question mentions *unnamed* makes a whole difference, as it means that the compiler *knows* that the object being constructed will be returned (since the construction is *in* the return statement). As with any optimization, it is not guaranteed, but all compilers do. – David Rodríguez - dribeas Dec 07 '11 at 07:04
  • 1
    Why do you think that in the last example RVO would be a pesimization?? Whether the `if` exists in the final compiled code or not it does not really matter, the compiler can perform NRVO on the second branch and remove the first branch altogether. Even if it does not remove it, it can translate the code to: `Foo j = F(3); return j;` in both cases and elide the two copies anyway. Care to explain why you think it would not be able to do so? – David Rodríguez - dribeas Dec 07 '11 at 07:22
  • It may not be the greatest example. But the point is, if you have an 'if' where one case can be RVO'd and the other case requires exactly what the compiler would do if there's no RVO, the 'if' may be more expensive than the benefits of the RVO. So "RVO" is not always an optimization. The spec would either have to say the compiler must do it even when it's not an optimization or it must require the programmer to know when RVO is or is not an optimization on the particular platform. Again, it's just an example. It's simply not possible to mandate RVO without getting all kinds of silliness. – David Schwartz Dec 07 '11 at 13:17