Difference Between Returning Reference VS Returning Value C++

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()) is U
  • with U& f(), f() is an lvalue, decltype(f()) is U&
  • with U&& f(), f() is an xvalue, decltype(f()) is U&&

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, and
  • static_cast<U&&>(x) is an xvalue (this is what std::move does).

So, in summary, the following two statements are correct:

  • "The return type of f is U&."
  • "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]):
Sample Image

  • 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:

  1. What does std::move(object).funcname() mean?
  2. 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



Leave a reply



Submit