Most of the time, by returning a reference to an object you’re making the statement that the object referred to will be around as long as the object returning it is. Basically you’re saying: “here, you can use this as long as I’m around”. You’re also telling the caller that you’re returning an object that can be used; unless you’re doing something wrong the reference should not be null. This doesn’t necessarily mean that the object being referenced will be valid: you could be returning a reference to a
boost::optional and while the
optional object is usable (e.g. you can test it to see if it contains anything) it might be empty (aka invalid).
In order to return a reference when thread synchronization is a concern there are two conditions that need to be met: the object being referenced must be internally synchronized (i.e. synchronizes its own internal state) and no other thread can come along and invalidate the reference while the reference is in use. Really the first condition is a consequence of the second: in order to be thread safe the referenced object either needs to be immutable or internally synchronized. But if the object is immutable then in order to update it you have to replace it and this would invalidate any references to the object being replaced. The second condition – no reference invalidation – precludes this so we have to have internally synchronized objects, unless the object is set in stone and never changes. In order to handle immutable objects that can be updated you either have to return by value so the caller gets a copy or use
const to make sure that the replaced object is still usable by anyone who already has a reference to it.
Most of the time references are what your function arguments should be. Cases where this doesn’t apply include: small objects (better performance to pass by value), ownership transfer (pass by value and move, or pass a
unique_ptr), the function needs its own copy to mutate without affecting the original object (pass by value), or the argument is optional (pass a bare pointer or boost::optional). Otherwise using a reference probably works best: the caller doesn’t have to check the validity of the reference, there’s no copying going on, and performance matches bare pointers; it’s hard to beat. Just be sure to make judicious use of
const to let the callers know what your intentions are towards their objects.
The only time you should use references as member variables is when you can guarantee that the referenced object will outlive the object the member variable is part of. In my experience that doesn’t happen very often; normally it’s only true when the containing object has a short life on the stack so that its lifetime is easily reasoned about. If I see a reference as a member variable I tend to assume that the writer of the code did the due diligence to make the guarantee about the referenced object’s lifetime and just assume that the referenced object will be around in any context where I might be using the reference.
Next time: bare pointers (yep, they still have their uses).