class toto ()
...
toto getToto();
toto &getToto();
His concern is the that return by reference is more efficient than return by value. Personally, I do not think much about efficiency but more on the correctness and clearness of the code, so for a long time, I return everything by value except for very rare cases such as overloading the operator << , etc. His suggestion triggered me to investigate more this point.
Let's consider return by value first so we have something to compare with. So how to write a function that returns an object by value? Well it's simple.
toto getToto()
{
toto myToto;
// Here I can work with myToto.
return myToto;
}
Or if I really have very simple work to do with the myToto, I can write also:
toto getToto()
{
return toto();
}
Basically, we have the following ways to use the functions:
toto aToto = getToto(); // get a toto and hold it with aToto
toto aToto(getToto()); // construct aToto with the result of getToto with its copy constructor.
getToto(); // get a toto and do nothing with it. Not useful by valid call.toto aToto; aToto = getToto(); // assign the result of getToto to an existing object.
That is not all the story yet, we still have another way to call the getToto:
const toto &aRefToto = getToto();
What we are doing here is to bind a reference to a constant to the result of getToto, which is a temporary object so we are not allowed to call it without the 'const' like: toto &aRefToto = getToto(); (at least we cannot do it now with most modern compilers. ).
So which one is more efficient ? It depends on the compiler. Actually, this is a feature usually named as RVO, Return Value Optimization. C++ standard leaves this to the implementation of the compiler. Modern compilers such as gcc does quite well so all the first three examples and the last one have the same best performance: only one object is created in the function, so the constructor and destructor get called only once. Only the fourth example cost more, which is caused by the assignment.
So here is the output of a test program:
toto aToto = getToto():
toto constructor.
toto destructor.
toto aToto(getToto());
toto constructor.
toto destructor.
getToto():
toto constructor.
toto destructor.
toto aToto; aToto = getToto();
toto constructor.
toto constructor.
toto assignment.
toto destructor.
toto destructor.
const toto &aRefToto = getToto();
toto constructor.toto destructor.
It is amazing to know that when you call toto aToto = getToto() or toto aToto(getToto()), you have actually zero overhead, the constructor and destructor are called only once for the object that is created inside the function. Also it's amazing to know the difference (the penalty you are paying) when you write something like: toto aToto; aToto = getToto(); once more constructor and destructor and one more assignment!
CONCLUSION ONE: Use toto aToto = getToto() or toto aToto(getToto()), don't write toto aToto; aToto = getToto();
Now let's consider the cases when we return by reference.
Things are a little different when it comes to return by reference. Obviously, you are not allowed to return the reference of a temporary object, so the following form is invalid:
toto &getTotoRef()
{
return toto();
}
Now we only have on possibility to return a reference of an object:
toto &getTotoRef()
{
toto aToto;
return aToto;
}
As far as you remember that the reference is "just another name of the referred object", you may notice that we are doing a dangerous thing here by returning a reference of a local object. Absolutely, you are right. A decent compiler will warn you that you are returning a reference to a local variable. Personally, I never return reference to a local object. This is just to demonstrate the efficiency aspect.
So here we have three ways to use the getTotoRef().
toto &aRefToto = getTotoRef(); // Don't do like this!
toto aToto = getTotoRef(); // construct with result of getTotoRef()
toto aToto(getTotoRef()); // construct with copy constructor.
getTotoRef(); // useless, but valid.
toto aToto; aToto = getTotoRef(); // assign result of getTotoRef() to an existing variable.
Here is the output of the call sequence when the test program runs:
toto &aRefToto = getTotoRef():
toto constructor.
toto destructor.
toto aToto = getTotoRef();
toto constructor.
toto destructor.
toto copy constructor.
toto destructor.
toto constructor.
toto destructor.
toto copy constructor.
toto destructor.
toto constructor.
toto destructor.
toto aToto; aToto = getTotoRef();
toto constructor.toto constructor.
toto destructor.
toto assignment.
toto destructor.
So if you use a reference to hold the result of getTotoRef, you have the maximal gain of return by reference: there is no overhead at all. Otherwise, either the copy constructor or the assignment need to be called to construct a new object based on the result of getTotoRef. However, with the first form which is most efficient here, you are bound to have unexpected behavior later when you want to use your reference so don't do that.
CONCLUSION TWO: Don't return a reference to a local variable, except when you know what you are doing :D
So why we have this feature to return a reference in C++? Well, this allows you to do something like:
toto &forwardToto(toto &comingToto)
{
return comingToto;
}
So here you don't need to create any object. The interest here is that if a function returns a reference, it can appear at the left side of an operator, so the following is valid:
forwardToto() = aToto;
And opens the possibility to do something like cout << "blabla" << endl; because << is overloaded to return a reference. For more on how to overload the operator <<, you should take a/another look at the great book by Prata: C++ Primer Plus (5th Edition)
We can also profit this feature in a member function of a class, but with a lot of care. So think about the tradeoff first.
Suppose we have a class with a member variable, and we have a getter of this variable, so we can make the getter return a reference, like:
class totoClass
{
public:
toto &getTotoRef();
_myToto;
}
Then you have the following ways to use this getter:
toto bToto = aToto.getTotoRef(); // Construct bToto.
toto bToto(aToto.getTotoRef()); // Construct bToto.
toto &bToto = aToto.getTotoRef(); // bToto refers to aToto._myToto.
bToto = aToto.getTotoRef(); // assign aToto._myToto to an existing bToto.
What do we pay for each call? Here is the result:
toto bToto = aToto.getTotoRef();
toto copy constructor.
toto destructor.
toto bToto(aToto.getTotoRef());
toto copy constructor.
toto destructor.
toto &bToto = aToto.getTotoRef();
** Nothing is called.
bToto = aToto.getTotoRef();
toto assignment.
OK, not bad, we call once the copy constructor when we want to save the result in another object, fair enough. If we only get the reference as in the third example, we don't need to call any toto constructor/copy constructor/assignment/destructor. That is a big saving if these operations are costly themselves. However, you can do this ***only if you are able to make sure that your totoClass object lives longer than your variable holding the result.
In comparison, tradition getters that return a value is not too bad. Here is what we have:
class totoClass
{
public:
toto getToto(); // get by value.
_myToto;
}
toto bToto = aToto.getToto()
toto copy constructor.
toto destructor.
toto bToto(aToto.getToto());
toto copy constructor.
toto destructor.
const toto &bToto = aToto.getToto();
toto copy constructor.
toto destructor.
bToto = aToto.getToto();
toto copy constructor.
toto assignment.
toto destructor.
So the penalty is at least one copy constructor need to be called. We no longer have the amazing gain when we have with ordinary functions that return a local object by value.
CONCLUSION THREE: Let member function return a reference to a member variable when the performance is really an issue, and call it with toto &aTotoRef = aToto.getTotoRef() to really take advantage of return by reference. Otherwise, return by value. Always take care of the variable holding the reference result, use it only during the life time of the object itself.
Hope this make things clearer, then we can investigate even more in the next part, yes, the story does not end here.
Like to know what you are thinking about this.
Like to know what you are thinking about this.