Community Pick: Many members of our community have endorsed this article.
Editor's Choice: This article has been selected by our editors as an exceptional contribution.

Return Value Optimization techniques implemented by C++ compilers

evilrixSenior Software Engineer (Avast)
CERTIFIED EXPERT
An expert in cross-platform ANSI C/C++ development, specialising in meta-template programming and low latency scalable architecture design.
Published:
Updated:
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.

// Example of RVO
Bar Foo()
{
	return Bar();
}

Open in new window


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.

// Example of NRVO
Bar Foo()
{
	Bar bar;
	return bar;
}

Open in new window


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 
#include <iostream>
 
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;
}

Open in new window

7
7,070 Views
evilrixSenior Software Engineer (Avast)
CERTIFIED EXPERT
An expert in cross-platform ANSI C/C++ development, specialising in meta-template programming and low latency scalable architecture design.

Comments (2)

CERTIFIED EXPERT

Commented:
In case somebody wonders how relevant this is for a C++ programmer.
It is highly relevant, not only when optimizing, also when designing your classes.

We use a lot of 3D math code, using points/vectors/matrices.
Without this kind of optimization, we could not simply write (without performance hit):

  point = matrix * point;

we would have to write something like:

  multiply(matrix, point_input, point_result);

taking a reference to point_result, so there is no temporary object and no unnecessary copy.
This latter version can get very ugly for long formulas.

So this really matters for C++ programmers, great article Rix :)
CERTIFIED EXPERT
Author of the Year 2009

Commented:
Great Article.  You got my Yes vote!

Have a question about something in this article? You can receive help directly from the article author. Sign up for a free trial to get started.

Get access with a 7-day free trial.
You Belong in the World's Smartest IT Community