Return Value Optimization

In days of old, returning something by value from a function in C++ was necessarily avoided because it would, invariably, involve one or even two copies of the object being created and potentially costly calls to a copy-constructor and destructor. Advances in compiler optimizations have all but eliminated this concern thanks to a clever set of optimizations implemented by most modern compilers.

The C++ standard allows the omission of the call to the copy constructor and, thus, allows the compiler to create a return value in the stack-frame of the calling function. This has the effect of allowing the compiler to treat both objects (in the caller and the callee) as the same entity, thus eliminating the need to take a copy.

There are two versions of this optimization available, Named Return Value Optimization (NRVO) and Return Value Optimization (RVO). Although the end result is the same, the syntax and semantics of each is slightly different:

RVO: Return Value Optimization is carried out when an object is constructed in-line within the return statement of a function, which would normally result in a temporary object being created on the stack, which is then copied into the calling functions stack-frame. When RVO is performed the object is created within the stack-frame of the calling function, thus avoiding the creation and destruction of an unnecessary temporary and the invocation of a copy constructor.

Bar Foo()
{
   return Bar();
}

Without RVO

Items constructed: 2
Items destructed: 1
Copies taken : 1

With RVO

Items constructed: 1
Items destructed: 0
Copies taken : 0

NRVO: Named Return Value Optimization is carried out when an object is created with a name within the called function and is then returned by name, which would normally result in a temporary object being copied on the stack, which is then copied into the calling functions stack-frame. When NRVO is performed the named object is created within the stack-frame of the calling function, thus avoiding the creation and destruction of an unnecessary temporary and the invocation of, potentially, two copy constructors.

Bar Foo()
{
   Bar bar;
   return bar;
}

Without NRVO

Items constructed: 3
Items destructed: 2
Copies taken : 2

With NRVO

Items constructed: 1
Items destructed: 0
Copies taken : 0

It should be obvious by now that when RVO or NRVO are used the copy-constructor on the returned object may not be called. For this reason it is very important that you do not write code that relies on the calling of a copy-constructor (such as instance counting, for example) since it may or may not be called depending upon the compiler, the optimization level and the way the function is written.

Each compiler implements support for RVN and NRVO to varying degrees so it is important to refer to your favourite compilers documentation to establish how well supported these two optimizations are.

It is not always possible for a compiler to carry out NRVO, code must be written to facilitate it. Again, this does vary from compiler to compiler but if there are multiple return paths you can be pretty sure NRVO will not take place.

// Example of N/RVO

struct MyClass
{
   MyClass()
   {
      std::cout << "MyClass::c_tor()" << std::endl;
   }

   MyClass(MyClass const &)
   {
      std::cout << "MyClass::cc_tor()" << std::endl;
   }

   ~MyClass()
   {
      std::cout << "MyClass::d_tor()" << std::endl;
   }
};

MyClass NRVO()
{
   std::cout << "Named Return value Optimization" << std::endl;

   MyClass myClass;
   return myClass;
};

MyClass RVO()
{
   std::cout << "Return value Optimization" << std::endl;

   return MyClass();
};

MyClass NoNRVO()
{
   std::cout << "** NO *** Named Return value Optimization -- this is unlikely to optimize" << std::endl;

   if(0)
   {
      MyClass myClass;
      return myClass;
   }
   else
   {
      MyClass myClass;
      return myClass;
   }
};

MyClass NoRVO()
{
   std::cout << "** NO *** Return value Optimization ??? -- this should still optimize" << std::endl;

   if(0)
   {
      return MyClass();
   }
   else
   {
      return MyClass();
   }
};

int main(void)
{
   std::cout <>> START >>>" << std::endl;

   MyClass myClass1 = NRVO();
   MyClass myClass2 = RVO();

   MyClass myClass3 = NoNRVO();
   MyClass myClass4 = NoRVO();
   std::cout << "<<< END <<<" << std::endl;
}

One thought on “Return Value Optimization

  1. Pingback: evilrix

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.