What does it mean to return a reference?
Asked Answered
B

6

12

I understand the concept of references in C++, and I understand what they do when used in function parameters, but I am still very much confused on how they work with return types.

For example, when used in parameters, this code:

int main (void) {
  int foo = 42;
  doit(foo);
}

void doit (int& value) {
  value = 24;
}

is similar to this code:

int main (void) {
  int foo = 42;
  doit(&foo);
}

void doit (int* value) {
  *value = 24;
}

(knowing that the compiler will automatically put an asterisk in front of value every time it is used in the first code sample of doit, but in the latter you'd have to put the asterisk in yourself every time you try to use value)

So when used as a reference what does this next code (using reference in a return type) translate to? Does it return a pointer to an int? Or would it just return an int?

int main (void) {
  int* foo = /*insert useful place in memory*/;
  foo = doit(foo);
}

int& doit (int* value) {
  //insert useful code
}
Bouse answered 5/12, 2012 at 7:42 Comment(2)
It would return a reference to an int, A reference you can think of as a named pointer, but without all the raw power of a pointer. The big problem with all of that , returning a reference to an object that quite possibly is created on the stack is that your going to have some nasty memory issues when the function returns and the functions stack is cleaned up. So , just be aware that just because it's a reference, that dose NOT mean that you wont have memory issues, and with references, it's really worse because the memory issue is hidden.Adlay
Not enough for an answer, but the best way to think of a reference is to think of it as another name for an object, or, as Luchian said, an alias.Engvall
T
25

It means you return by reference, which is, at least in this case, probably not desired. It basically means the returned value is an alias to whatever you returned from the function. Unless it's a persistent object it's illegal.

For example:

int& foo () {
    static int x = 0;
    return x;
}

//...
int main()
{
    foo() = 2;
    cout << foo();
}

would be legal and print out 2, because foo() = 2 modifies the actual value returned by foo.

However:

int& doit () {
    int x = 0;
    return x;
}

would be illegal (well, accessing the returned value would), because x is destroyed when the method exits, so you'd be left with a dangling reference.

Returning by reference isn't common for free functions, but it is for methods returning members. For example, in the std, the operator [] for common containers return by reference. For example, accessing a vector's elements with [i] returns an actual reference to that element, so v[i] = x actually changes that element.

Also, I hope that "is essentially equal to this code" means that they're semantically sort of (but not really) similar. Nothing more.

Tristram answered 5/12, 2012 at 7:47 Comment(6)
Thanks Luchian, that helped a lot! And yes, I meant "similar". I'll edit that for future references.Bouse
Just to help clear up, return by reference returns the address of whatever object you attempted to return. You obviously don't want to return the address of an object with local scope because the object will be destroyed when the function called is done, which will leave you with a dangling reference (which would in turn may cause a use-after-free bug). Therefore, a good use of return by reference would be, as Luchian said, when returning a member.Bouse
@MrMeganFox returning by reference doesn't return the address, it returns an alias.Tristram
@LuchianGrigore: no it returns a reference, not an alias. References are usually implemented by using machine addresses under the hood.Adrial
@ChrisDodd a reference is an "alias" of the object it refers to - a different name for the same object.Tristram
The object doesn't need to be persistent, the lifetime of the object needs to be greater than the function scope, which can, for example be via lifetime extension.Presumptuous
S
4

It means that you return a pointer to the memory address where the correspondent data is, instead of the very data.

Staves answered 5/12, 2012 at 20:40 Comment(0)
C
1

Assuming this code (to make it comparable to the first example) :

int main (void) {
  int* foo = /*insert useful place in memory*/;
  *foo = doit(foo);
}

int& doit (int* value) {
  *value = 24;
  return *value;
}

The int& is not really useful as a return type in this case, because it provides access to the variable in memory (of which you pass the pointer to the function).

Does it return a pointer to an int? Or would it just return an int?

No, it returns a reference to an int. If you want, you can look at it as a pointer which can not be nullptr.

Clovah answered 5/12, 2012 at 8:2 Comment(0)
Y
0

Hmm, the best way to know the answer is to have a try...

Your codes will not pass type check, because doit will return a reference of int while you accept the return value as a pointer of int.

You can have a look at this:

#include<iostream>
using namespace std;
int& doit (int* value) {
    value[0] = 3;
    return value[4];
}
int main (void) {
  int* foo = new int[10];
  for (int i=0; i<10; i++)
    foo[i] = i;
  int& bar = doit(foo);
  cout<<bar<<endl;
  for (int i=0; i<10; i++)
      cout<<foo[i]<<" ";
  cout<<endl;
  bar = 12;
  for (int i=0; i<10; i++)
      cout<<foo[i]<<" ";
  cout<<endl;
  return 0;
}

The variable "bar" will accept the return value, and it can be used change the content of "foo". As is mentioned by Luchian, it maybe dangerous to return a reference from the function, because the later codes might modify a value which is in the stack.

Yardstick answered 5/12, 2012 at 7:58 Comment(0)
L
0

Another useful example is operator overloading.

class vec2 {
public:
     vec2 operator+(const vec2& right_hand_side){
          return vec2(*this) += right_hand_side;
     }
     vec2& operator+=(const vec2& right_hand_side){
          x += right_hand_side.x;
          y += right_hand_side.y;
          return *this;
     }
 public:
 int x;
 int y;
}

In this example we have a vec2 class that represents a 2D vector. Now we defined two operators for this class, +, and +=. We want the + operator to return a copy of a vec2 to avoid a case where adding two operands results in changing the value of the operands.

vec2 a(1, 1);
vec2 b(5, 5);
vec2 c;
c = a + b;

Here, we want to assign c to (a + b), without affecting the values of a or b. However, in the case of the += operator, we want the operation to affect the object that is calling this operator, that object being one of the operands.

vec2 c(2, 2);
c += a; // c = (3, 3) a = (1, 1)

So, we can return a reference to the object we want affected by the operation. If we did not return a reference, both values would be copied in place. If we made both operators return a reference, then any + operation between two vec2s would affect the operands.

For example, if we changed

vec2& operator+(const vec2 right_hand_side){
     return *this += right_hand_side;
}

did the same operation,

c = a + b; // c = (6, 6), a = (6, 6)

c would be set to (6, 6), but so would a, since we would be returning a reference in the + operator.

A little clarification if that was confusing, but with operator overloading we the object that calls the operator is the left hand operand so when we do a + b, a is the *this that we are referring to, and the vec2 reference we are returning.

Lilililia answered 12/5, 2023 at 15:54 Comment(0)
S
0

There's an application for this pattern that isn't addressed in the answers given, so I thought I'd share.

One common pattern where return-by-reference is useful is when the following are true:

  • Your function receives a reference variable
  • Your function modifies the reference variable in some way
  • You wish to be able to then use the value in further statements as though it were returned

Take this example:

#include <iostream>

int add_two(int operand){
    return operand+2;
}

int main(){
    int a{3};
 
    std::cout << add_two(a) << std::endl;

    return 0;
}

In this example, add_two receives by copy the value in a. Then it adds two to it, then it returns by copy the result.

For a simple integer, that's fine. No problem. Maybe faster than passing pointers/references, honestly. But if you were sending in a very large structure of some kind, that kind of copying could be onerous or even impossible. Also, it's important to note that we did not change the value of a. So while this will work as expected with respect to outputting a value, a was not changed, and that's one of the desired effects of add_two.

If we refactor it to this:

#include <iostream>

void add_two(int &operand){
    operand += 2;
}

int main(){
    int a{3};
    add_two(a);

    std::cout << a << std::endl;

    return 0;
}

Then now we're passing a reference to a into the add_two, and we're adding two to that. That solves the copying issue: we're no longer copying the hypothetical large data structure, and it adds the missing functionality: a is changed. That's good and all, but we can't use add_two inline. We have to have two separate lines. So we further refactor:

#include <iostream>

int & add_two(int &operand){
    operand += 2;
    return operand;
}

int main(){
    int a{3};

    std::cout << add_two(a) << std::endl;

    return 0;
}

Now, we're not copying anything, and since the function doesn't return void, we can still use it elsewhere.

This pattern is very common when creating custom unary operators for data types. The ++() and --() (pre-operation) operators both return references to their operands (see here). All of the << and >> operators related to stream functionality receive streams by reference, modify them, then return references to the stream so that they can be strung together.

In general, you must return something that will persist after the function exits. If you simply return a value, that value will persist because it's been copied out. But if you return a reference, that reference has to exist once the function's scope goes away. It's a picky, specific use case, and one that isn't likely to come up often. But this is one example.

Songwriter answered 10/6, 2024 at 19:7 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.