We all know that returning a reference to a local variable is a bad idea. However, I'm wondering if it's ever really a good idea to a return a reference at all and if it's possible to determine some good rules about when or when not to do it.
My problem with returning a reference is that the calling function needs to care about the lifetime of an object that shouldn't be its responsibility. As a contrived example:
#include <vector>
const int& foo() {
std::vector<int> v = {1, 2, 3, 4, 5};
return v[0];
}
int main(int argc, const char* argv[])
{
const int& not_valid = foo();
return 0;
}
Here, the vector
goes out of scope at the end of foo
, destroying its contents and invalidating any references to its elements. vector::operator[]
returns a reference to the element, and so when this reference is further returned out of foo
, the reference in main
is dangling. I don't believe the const reference will extend the lifetime here because it's not a reference to a temporary.
As I said, this is a contrived example and the writer of foo
probably wouldn't be so silly to try and return v[0]
as a reference. However, it's easy to see how returning a reference requires the caller to care about the lifetime of an object it doesn't own. Pushing an element into a vector
copies it, so then the vector
is responsible for it. This problem doesn't exist for passing a reference argument because you know the function will complete before the caller continues and destroys the object.
I can see that returning a reference allows some nice array-like syntax like v[0] = 5
- but what's so bad about having a member function like v.set(index, value)
? At least with this we wouldn't be exposing the internal objects. I know there may also be a performance increase from returning a reference, but with RVO, Named RVO (NRVO), and move semantics it is either negligible or non-existent.
So I've been trying to imagine under which situations returning a reference is ever truly safe, but I can't get my head around all the different permutations of ownership semantics that it might involve. Are there any good rules on when to do this?
Note: I know a better way to deal with ownership in vector
s is to use smart pointers, but then you get the same problem with a different object - who owns the smart pointer?