Item 20: Facilitate the return value optimization.
A function that returns an object is frustrating to efficiency aficionados, because the by-value return, including the constructor and destructor calls it implies (see Item 19), cannot be eliminated. The problem is simple: a function either has to return an object in order to offer correct behavior or it doesn't. If it does, there's no way to get rid of the object being returned.
Consider the operator*
function for rational
class Rational { public: Rational(int numerator = 0, int denominator = 1); ... int numerator() const; int denominator() const; }; // For an explanation of why the return value is const, // see Item 6 const Rational operator*(const Rational& lhs, const Rational& rhs);
Without even looking at the code for operator*
, we know it must return an object, because it returns the product of two arbitrary numbers. These are arbitrary numbers. How can operator*
possibly avoid creating a new object to hold their product? It can't, so it must create a new object and return it. C++ programmers have nevertheless expended Herculean efforts in a search for the legendary elimination of the by-value return (see Items E23 and E31).
Sometimes people return pointers, which leads to this syntactic
// an unreasonable way to avoid returning an object const Rational * operator*(const Rational& lhs, const Rational& rhs); Rational a = 10; Rational b(1, 2); Rational c = *(a * b); // Does this look "natural" // to you?
It also raises a question. Should the caller delete the pointer returned by the function? The answer is usually yes, and that usually leads to resource
Other developers return references. That yields an acceptable
// a dangerous (and incorrect) way to avoid returning // an object const Rational& operator*(const Rational& lhs, const Rational& rhs); Rational a = 10; Rational b(1, 2); Rational c = a * b; // looks perfectly reasonable
but such functions can't be implemented in a way that behaves correctly. A common attempt looks like
// another dangerous (and incorrect) way to avoid // returning an object const Rational& operator*(const Rational& lhs, const Rational& rhs) { Rational result(lhs.numerator() * rhs.numerator(), lhs.denominator() * rhs.denominator()); return result; }
This function returns a reference to an object that no longer exists. In particular, it returns a reference to the local object result
, but result
is automatically destroyed when operator*
is exited. Returning a reference to an object that's been destroyed is hardly
Trust me on this: some functions (operator*
among them) just have to return objects. That's the way it is. Don't fight it. You can't
That is, you can't win in your effort to eliminate by-value returns from functions that require them. But that's the wrong war to wage. From an efficiency point of view, you shouldn't care that a function returns an object, you should only care about the cost of that object. What you need to do is channel your efforts into finding a way to reduce the cost of returned objects, not to eliminate the objects themselves (which we now recognize is a futile quest). If no cost is associated with such objects, who cares how many get
It is frequently possible to write functions that return objects in such a way that compilers can eliminate the cost of the temporaries. The trick is to return constructor arguments instead of objects, and you can do it like
// an efficient and correct way to implement a // function that returns an object const Rational operator*(const Rational& lhs, const Rational& rhs) { return Rational(lhs.numerator() * rhs.numerator(), lhs.denominator() * rhs.denominator()); }
Look closely at the expression being returned. It looks like you're calling a Rational
constructor, and in fact you are. You're creating a temporary Rational
object through this
Rational(lhs.numerator() * rhs.numerator(), lhs.denominator() * rhs.denominator());
and it is this temporary object the function is copying for its return
This business of returning constructor arguments instead of local objects doesn't appear to have bought you a lot, because you still have to pay for the construction and destruction of the temporary created inside the function, and you still have to pay for the construction and destruction of the object the function returns. But you have gained something. The rules for C++ allow compilers to optimize temporary objects out of existence. As a result, if you call operator*
in a context like
Rational a = 10; Rational b(1, 2); Rational c = a * b; // operator* is called here
your compilers are allowed to eliminate both the temporary inside operator*
and the temporary returned by operator*
. They can construct the object defined by the return
expression inside the memory allotted for the object c
. If your compilers do this, the total cost of temporary objects as a result of your calling operator*
is zero: no temporaries are created. Instead, you pay for only one constructor call the one to create c
. Furthermore, you can't do any better than this, because c
is a named object, and named objects can't be eliminated (see also Item 22).7 You can, however, eliminate the overhead of the call to operator*
by declaring that function inline
(but first see Item E33):
// the most efficient way to write a function returning // an object inline const Rational operator*(const Rational& lhs, const Rational& rhs) { return Rational(lhs.numerator() * rhs.numerator(), lhs.denominator() * rhs.denominator()); }
"Yeah, yeah," you mutter, "optimization, schmoptimization. Who cares what compilers can do? I want to know what they do do. Does any of this nonsense work with real compilers?" It does. This particular optimization eliminating a local temporary by using a function's return location (and possibly replacing that with an object at the function's call site) is both well-known and commonly implemented. It even has a name: the return value optimization. In fact, the existence of a name for this optimization may explain why it's so widely available. Programmers looking for a C++ compiler can ask vendors whether the return value optimization is implemented. If one vendor says yes and another says "The what?," the first vendor has a notable competitive advantage. Ah, capitalism. Sometimes you just gotta love
operator*
above may now yield the same (optimized) object code.