More Effective C++ | Item 20: Facilitate the return value optimization Back to Item 19: Understand the origin of temporary objects Continue to Item 21: Overload to avoid implicit type conversions 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. Period. Consider the operator* function for rational numbers: 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 travesty: // 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 leaks. Other developers return references. That yields an acceptable syntax, // 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 this: // 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 useful. 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 win. 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 created? 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 this: // 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 expression, Rational(lhs.numerator() * rhs.numerator(), lhs.denominator() * rhs.denominator()); and it is this temporary object the function is copying for its return value. 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 this, 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 it. Back to Item 19: Understand the origin of temporary objects Continue to Item 21: Overload to avoid implicit type conversions 7 In July 1996, the ISO/ANSI standardization committee declared that both named and unnamed objects may be optimized away via the return value optimization, so both versions of operator* above may now yield the same (optimized) object code. Return