Difference between returning reference vs returning value C++
The difference is that, when you return a reference, you can assign to the result of GetVal()
:
myObj.GetVal() = 42;
You can also keep the returned reference around, and use it to modify myObj.val
later.
If GetVal()
were to return val
by value, none of this would be possible.
Whether any of this is desirable, or indeed good design, is a different question altogether.
Note that your example is very different to the code in the linked question -- that code returns an invalid reference and is unequivocally a bad idea.
The difference between returning an object by value and by reference,object construction
You cannot "return a reference". The phrase is a colloquialism that obscures details that matter if you actually care about the details.
A function call expression (e.g. f(a, b, c)
) is an expression, and when evaluated it produces a value. A value is never a reference. All that matters about a value, apart from its type, is its value category, i.e. whether it is an lvalue, an xvalue or a prvalue.
There is a common way in C++ to encode a value category in the type system, which is used for function return types, casts, and for decltype
. It goes like this. Assume that U
is an object type.
- with
U f()
,f()
is a prvalue,decltype(f())
isU
- with
U& f()
,f()
is an lvalue,decltype(f())
isU&
- with
U&& f()
,f()
is an xvalue,decltype(f())
isU&&
What's important here is that f
always "returns a U
", but what matters is which value is returned.
Moreover, given a glvalue x
of type U
,
static_cast<U>(x)
is a prvalue (a "copy" or a "load"),static_cast<U&>(x)
is an lvalue, andstatic_cast<U&&>(x)
is an xvalue (this is whatstd::move
does).
So, in summary, the following two statements are correct:
- "The return type of
f
isU&
." - "The function
f
returns an lvalue."
Colloquially, people will talk of "returning a reference", but what they really mean is "returning a glvalue" (because the two reference return types are used for the two kinds of glvalue). The distinction between glvalue and prvalue is the one we usually care most about, because a glvalue is an existing location, whereas a prvalue is a guarnanteed-unique new copy.
Return by value vs return by reference?
Since it's a feedback request, here are my 2 cents.
There are two aspects I would like to focus on: performance and safety.
Performance
Returning by reference provides performance improvement by removing extra-copying. Amount of improvement strongly depends on the usage: how big is the object, how tight is the loop, etc.
To bring in some data: here's a benchmark on-line (note: BigDataStruct
holds char[256]
):
Returning by reference has an impact. It's up to you to decide if it is significant on case-to-case basis.
When doing
BigDataStruct someData = someDataGiver.getByConstRef();
performance is the same
Safety
Returning by reference is not memory-safe and is not thread-safe since it provides aliasing to data (BigDataStruct
) that could be potentially modified elsewhere (e.g., in DataGiver
).
To illustrate it with an example:
const BigDataStruct& someData = someDataGiver.getByConstRef();
// do something with someData
someDataGiver.modifyData(); //<= someData is now changed!
// doing same things with someData can have different outcome
You might think: no sane developer will write anything like that. But here's one that can happen quite often (happened with me):
const BigDataStruct& someData =
DataGiver().getByConstRef(); // Bam! A wild dangling reference appears!
Conclusions
Returning by reference can improve performance. But it has few nasty pitfalls that developer has to keep track of. Whether to discourage it or not is a strong recommendation to make. IMO defaulting to returning-by-value and motivating returning-by-reference could be a balanced approach. Also explicitly stating that the value is returned by reference is a good idea (which your team already does with ByConstRef
postfix).
Update: as @LightnessRacesinOrbit mentioned in the comment (also see answer by Pavlo K): if you don't have the control of all the getter usages (e.g., developing a library or having external users) returning by reference provides more flexibility by delegating the choice to the final user.
C: When to return by value or pass reference
Start by deciding which approach makes the most sense at the logical level, irrespective of what you think the performance implications might be. If returning a struct
by value most clearly conveys the intent of the code, then do that.
This isn't the 1980s anymore. Compilers have gotten a lot smarter since then and do a really good job of optimizing code, especially code that's written in a clear, straightforward manner. Similarly, parameter passing and value return conventions have become fairly sophisticated as well. The simplistic stack-based model doesn't really reflect the reality of modern hardware.
If the resulting application doesn't meet your performance criteria, then run it through a profiler to find the bottlenecks. If it turns out that returning that struct
by value is causing a problem, then you can experiment with passing by reference to the function.
Unless you're working in a highly constrained, embedded environment, you really don't have to count every byte and CPU cycle. You don't want to be needlessly wasteful, but by that same token you don't want to obsess over how things work at the low level unless a) you have really strict performance requirements and b) you are intimately familiar with the details of your particular platform (meaning that you not only know your platform's function calling conventions inside and out, you know how your compiler uses those conventions as well). Otherwise, you're just guessing. Let the compiler do the hard work for you. That's what it's there for.
Difference between returning an rvalue reference and a value in && qualified functions
When you use this
reference qualifiers in this way, you have to ask yourself two questions:
- What does
std::move(object).funcname()
mean? - What does
Typename().funcname()
mean?
If you return a value from the function, then both of those will mean the same thing. Regardless of what you do to capture the value, the value will be a whole and distinct object, move-constructed from some internal data stored in the object.
In the first case, object
now potentially no longer owns the data. In the second case, it doesn't matter because the object was a temporary and has since been destroyed.
If you return a &&
from the function, then those will mean different things. Namely, #2 will mean "your code is broken".
As to why you might still want to do it, even if it allows broken code. Well, that has to do with the actual answers to that question: what do those things mean?
Here's what I am referring to. std::get
is basically a member function of tuple
. And yet, if you pass it a tuple&&
, you will get a T&&
returned and not a T
. Why?
Because you're accessing a member of the tuple
.
If you had a struct
, then std::move(struct_object).x
would be an rvalue reference as well. So std::get
is simply behaving in the same way for a tuple
as member access would for a struct
. That's kinda the whole point of tuple
, after all: to behave like a struct
as much as possible.
This allows you to do things like std::get<0>(std::move(tpl)).member
, such that member
will still be an rvalue reference. So you can move from a subobject without disturbing the rest of the object, exactly as you could for any other rvalue reference member accesses.
If get
returned a value, then this would do something very different. Regardless of what we do with the return value, it will be moved out of the object, no questions asked. So if we only wanted to move a subobject of that member... too bad. The original object lost the entire member, not just a subobject of that member.
Of course, that doesn't change the fact that:
auto &&x = SomeStruct().x; //This extends the temporary's lifetime
auto &&x = std::get<0>(SomeTuple(...)); //This gets a dangling reference.
That is an unfortunate limitation of the language. But if the function is logically a member accessor, then returning a &&
from a &&
qualified this
function instead of a value is a legitimate choice.
It all depends on what matters more to you: safety or orthogonality with member accessors.
Return rvalue reference vs return by value in function return type
Returning a reference to an object being about to be destroyed is always wrong: the referenced object will be destroyed before it can be used in any form. Making the reference an rvalue reference doesn't change that, it just makes returning a temporary compile.
Note that returning a temporary string by value will probably result in copy elision, i.e., the object is probably going to be constructed directly in the location where it is used. If that doesn't happen the object will be moved if there is a move constructor for the return type (there is for std::string
). Note that the same applies when returning a function local variable instead of the temporary.
Related Topics
C++ Short-Circuiting of Booleans
How to Use Windbg to Analyze the Crash Dump for Vc++ Application
Best Method for Storing This Pointer for Use in Wndproc
Does "Std::Size_T" Make Sense in C++
C++ Multiple Inheritance Function Call Ambiguity
How to Automatically Register a Class on Creation
Cmake Find_Package Specify Path
How Typedef Works for Function Pointers
Why How to Use 'Std::Move' on a 'Const' Object
Is There Actually a Reason Why Overloaded && and || Don't Short Circuit
Segmentation Fault at Glgenvertexarrays( 1, &Vao );
Do C++ Templates Make Programs Slow