tags:

views:

202

answers:

6

I am trying to understand how to use reference parameters. There are several examples in my text, however they are too complicated for me to understand why and how to use them. Could anyone give me the MOST basic example of how/why to use one, and perhaps the difference with or without it (what would happen if you didn't attach the '&').

for example, if I've created a function: int doSomething(int& a, int& b), what would be the consequences of not putting in that '&.'

I understand that reference variables are used in order to change a formal->reference, which then allows a two-way exchange of parameters. However, that is the extent of my knowledge, and a more concrete example would be of much help. Thank you.

+14  A: 

Think of a reference as an alias. When you invoke something on a reference, you're really invoking it on the object to which the reference refers.

int i;
int& j = i; // j is an alias to i

j = 5; // same as i = 5

When it comes to functions, consider:

void foo(int i)
{
    i = 5;
}

Above, int i is by-value. That means if we say:

int x = 2;
foo(x);

i will be a copy of x. Thus setting i to 5 has no effect on x, because it's the copy of x being changed. However, if we make i a reference:

void foo(int& i) // i is an alias for a variable
{
    i = 5;
}

Then saying foo(x) no longer makes a copy of x; i is x. So if we say foo(x), inside the function i = 5; is exactly the same as x = 5;, and x changes.

Hopefully that clarifies a bit.


Why is this important? When you program, you never want to copy and paste code. You want to make a function that does one task and it does it well. Whenever that task needs to be performed, you use that function.

So let's say we want to swap two variables. Just to clarify, that looks something like this:

int x, y;

// swap:
int temp = x; // store the value of x
x = y; // make x equal to y
y = temp; // make y equal to the old value of x

Okay, great. We want to make this a function, because: swap(x, y); is much easier to read. So, let's try this:

void swap(int x, int y)
{
    int temp = x;
    x = y;
    y = temp;
}

This won't work! The problem is that this is swapping copies of two variables. That is:

int a, b;
swap(a, b); // hm, x and y are copies of a and b...a and b remain unchanged

In C, where references do not exist, the solution was to pass the address of these variables; that is, use pointers*:

void swap(int* x, int* y)
{
    int temp = *x;
    *x = *y;
    *y = temp;
}

int a, b;
swap(&a, &b);

This works well. However, it's a bit clumsy to use, and actually a bit unsafe. (swap(0, 0), swapping two nothings...undefined behavior!). Fixable of course with some checks:

void swap(int* x, int* y)
{
    if (!x || !y)
        return; // one or both is null

    int temp = *x;
    *x = *y;
    *y = temp;
}

But looks how clumsy our code has gotten. C++ introduces references to solve this problem. If we can just alias a variable, we get the code we were looking for:

void swap(int& x, int& y)
{
    int temp = x;
    x = y;
    y = temp;
}

int a, b;
swap(a, b); // inside, x and y are really a and b

Both easy to use, and safe. (We can't accidentally pass in a null, we'd have to try for that.) This works because the swap happening inside the function is really happening on the variables being aliased outside the function.

(Note, never write a swap function. :) One already exists in the header <algorithm>, and it's templated to work with any type.)


Another use is to remove that copy that happens when you call a function. Consider we have a data type that's very big. Copying this object takes a lot of time, and we'd like to avoid that:

struct big_data
{ char data[9999999]; }; // big!

void do_something(big_data data);

big_data d;
do_something(d); // ouch, making a copy of all that data :<

However, all we really need is an alias to the variable, so let's indicate that. (Again, back in C we'd pass the address of our big data type, solving the copying problem but introducing clumsiness.):

void do_something(big_data& data);

big_data d;
do_something(d); // no copies at all! data aliases d within the function

This is why you'll hear it said you should pass things by reference all the time, unless they are primitive types. (Because internally passing an alias is probably done with a pointer, like in C. For small objects it's just faster to make the copy then worry about pointers.)

Keep in mind you should be const-correct. This means if your function doesn't modify the parameter, mark it as const. If do_something above only looked at but didn't change big_data, we'd mark it as const:

void do_something(const big_data& data); // alias a big_data, and don't change it

We avoid the copy, and we say "hey, we don't be modifying this." This has other side effects (with things like temporary variables), but you shouldn't worry about that now.

In contrast, our swap function cannot be const, because we are indeed modifying the aliases.

Hope this clarifies some more.


*Rough pointers tutorial:

A pointer is a variable that holds the address of another variable. For example:

int i; // normal int

int* p; // points to an integer (is not an integer!)
p = &i; // &i means "address of i". p is pointing to i

*p = 2; // *p means "dereference p". that is, this goes to the int
        // pointed to by p (i), and sets it to 2.

So, if you've seen the pointer-version swap function, we pass the address of the variables we want to swap, and then we do the swap, dereferencing to get and set values.

GMan
+1 - great intro to references. Just wanted to add some clarity with regards to aliases. A good way to think about an alias is to think of it like a nickname. For example, let's say Jane has a nickname - Jill. You could say either Jane or Jill and still be referring to the same person. So above in the last example with the reference, you might say `i` is a nickname for `x` - it sounds different, but both are referring to the same variable. That is, they look different, but they are referring to the same location in memory.
Cam
+1 Not bad... For a unicorn hugger...
John Dibling
Sagistic
There are two main reasons you would want to use references. **Reason 1: Saving Memory** - In a normal function (no references), a copy of the passed variable(s) must be made. With an int as in the examples above, that's not a problem. But sometimes you might pass huge pieces of data, and the function simply wants to read that data, so making a second copy would be a waste fo memory - but using references allows the data to be read without using extra memory.
Cam
**Reason 2: Modifying the passed variable** - Sometimes, you may simply want to modify the variable passed to the function. A good example would be if you created a function called `divideByTwo`. Using references, you could allow the function to be called like this `divideByTwo(x)` and modify `x`. That's more concise than something like `x=divideByTwo(x)`.
Cam
(Sorry for the triple comment here; SO comment word limit). I also want to add that although functions using references can be great for the reasons listed above, there are still times when you shouldn't use them - especially with smaller datatypes - so don't go around using references everywhere if you don't need to :)
Cam
+3  A: 

Lets take a simple example of a function named increment which increments its argument. Consider:

void increment(int input) {
 input++;
}

which will not work as the change takes place on the copy of the argument passed to the function on the actual parameter. So

int i = 1;
std::cout<<i<<" ";
increment(i);
std::cout<<i<<" ";

will produce 1 1 as output.

To make the function work on the actual parameter passed we pass its reference to the function as:

void increment(int &input) { // note the & 
 input++;
}

the change made to input inside the function is actually being made to the actual parameter. This will produce the expected output of 1 2

codaddict
speaking of incrementing - I bet the ++ operator uses a reference.
Cam
@incrediman: increment and decrement operators don't result in a function call, and hence don't need references. When you overload them, they're usually methods, which use the `this` pointer.
outis
+1  A: 
// Passes in mutable references of a and b.
int doSomething(int& a, int& b) {
  a = 5;
  cout << "1: " << a << b;  // prints 1: 5,6
}

a = 0;
b = 6;
doSomething(a, b);
cout << "2: " << a << ", " << b;  // prints 2: 5,6

Alternatively,

// Passes in copied values of a and b.
int doSomething(int a, int b) {
  a = 5;
  cout << "1: " << a << b;  // prints 1: 5,6
}

a = 0;
b = 6;
doSomething(a, b);
cout << "2: " << a << ", " << b;  // prints 2: 0,6

Or the const version:

// Passes in const references a and b.
int doSomething(const int &a, const int &b) {
  a = 5;  // COMPILE ERROR, cannot assign to const reference.
  cout << "1: " << b;  // prints 1: 6
}

a = 0;
b = 6;
doSomething(a, b);

References are used to pass locations of variables, so they don't need to be copied on the stack to the new function.

Stephen
A: 

I don't know if this is the most basic, but here goes...

typedef int Element;
typedef std::list<Element> ElementList;

// Defined elsewhere.
bool CanReadElement(void);
Element ReadSingleElement(void); 

int ReadElementsIntoList(int count, ElementList& elems)
{
    int elemsRead = 0;
    while(elemsRead < count && CanReadElement())
        elems.push_back(ReadSingleElement());
    return count;
}

Here we use a reference to pass our list of elements into ReadElementsIntoList(). This way, the function loads the elements right into the list. If we didn't use a reference, then elems would be a copy of the passed-in list, which would have the elements added to it, but then elems would be discarded when the function returns.

This works both ways. In the case of count, we don't make it a reference, because we don't want to modify the count passed in, instead returning the number of elements read. This allows the calling code to compare the number of elements actually read to the requested number; if they don't match, then CanReadElement() must have returned false, and immediately trying to read some more would likely fail. If they match, then maybe count was less than the number of elements available, and a further read would be appropriate. Finally, if ReadElementsIntoList() needed to modify count internally, it could do so without mucking up the caller.

Mike DeSimone
@Mike: Thank you, however this is my first C++ class, and I don't understand 80% of your code. thanks though.
Sagistic
+1  A: 

A simple pair of examples which you can run online.

The first uses a normal function, and the second uses references:


Edit - here's the source code incase you don't like links:

Example 1

using namespace std;

void foo(int y){
    y=2;
}

int main(){
    int x=1;
    foo(x);
    cout<<x;//outputs 1
}


Example 2

using namespace std;

void foo(int & y){
    y=2;
}

int main(){
    int x=1;
    foo(x);
    cout<<x;//outputs 2
}
Cam
Thank you for the extensive elaboration of Gman's post. This will help very much. I will get back to you.
Sagistic
Alright. I get it now. It just makes it weird how in the example, the variable being declared within the function is y, but the function being called uses the parameter x, which then becomes a copy of y (when used with parameter). THanks again.
Sagistic
No problem. Post back if you have any more questions!
Cam
+3  A: 

GMan's answer gives you the lowdown on references. I just wanted to show you a very basic function that must use references: swap, which swaps two variables. Here it is for ints (as you requested):

// changes to a & b hold when the function exits
void swap(int& a, int& b) {
    int tmp = a;
    a = b;
    b = tmp;
}

// changes to a & b are local to swap_noref and will go away when the function exits
void swap_noref(int a, int b) {
    int tmp = a;
    a = b;
    b = tmp;
}

// changes swap_ptr makes to the variables pointed to by pa & pb
// are visible outside swap_ptr, but changes to pa and pb won't be visible
void swap_ptr(int *pa, int *pb) {
    int tmp = *pa;
    *pa = *pb;
    *pb = tmp;
}

int main() {
    int x = 17;
    int y = 42;
    // next line will print "x: 17; y: 42"
    std::cout << "x: " << x << "; y: " << y << std::endl

    // swap can alter x & y
    swap(x,y);
    // next line will print "x: 42; y: 17"
    std::cout << "x: " << x << "; y: " << y << std::endl

    // swap_noref can't alter x or y
    swap_noref(x,y);
    // next line will print "x: 42; y: 17"
    std::cout << "x: " << x << "; y: " << y << std::endl

    // swap_ptr can alter x & y
    swap_ptr(&x,&y);
    // next line will print "x: 17; y: 42"
    std::cout << "x: " << x << "; y: " << y << std::endl
}

There is a cleverer swap implementation for ints that doesn't need a temporary. However, here I care more about clear than clever.

Without references (or pointers), swap_noref cannot alter the variables passed to it, which means it simply cannot work. swap_ptr can alter variables, but it uses pointers, which are messy (when references won't quite cut it, however, pointers can do the job). swap is the simplest overall.

On Pointers

Pointers let you do some of the same things as references. However, pointers put more responsibility on the programmer to manage them and the memory they point to (a topic called "memory management"–but don't worry about it for now). As a consequence, references should be your preferred tool for now.

Think of variables as names bound to boxes that store a value. Constants are names bound directly to values. Both map names to values, but the value of constants can't be changed. While the value held in a box can change, the binding of name to box can't, which is why a reference cannot be changed to refer to a different variable.

Two basic operations on variables are getting the current value (done simply by using the variable's name) and assigning a new value (the assignment operator, '='). Values are stored in memory (the box holding a value is simply a contiguous region of memory). For example,

int a = 17;

results in something like (note: in the following, "foo @ 0xDEADBEEF" stands for a variable with name "foo" stored at address "0xDEADBEEF". Memory addresses have been made up):

             ____
a @ 0x1000: | 17 |
             ----

Everything stored in memory has a starting address, so there's one more operation: get the address of the value ("&" is the address-of operator). A pointer is a variable that stores an address.

int *pa = &a;

results in:

              ______                     ____
pa @ 0x10A0: |0x1000| ------> @ 0x1000: | 17 |
              ------                     ----

Note that a pointer simply stores a memory address, so it doesn't have access to the name of what it points to. In fact, pointers can point to things without names, but that's a topic for another day.

There are a few operations on pointers. You can dereference a pointer (the "*" operator), which gives you the data the pointer points to. Dereferencing is the opposite of getting the address: *&a is the same box as a, &*pa is the same value as pa, and *pa is the same box as a. In particular, pa in the example holds 0x1000; * pa means "the int in memory at location pa", or "the int in memory at location 0x1000". "a" is also "the int at memory location 0x1000". Other operation on pointers are addition and subtraction, but that's also a topic for another day.

outis
This is a really great example of how references can be practical. +1. I think you may want to remove the example with pointers though or explain it further - it might only serve to confused the OP and you already have the excellent example using references which does the same thing.
Cam
@incrediman: I feel ambivalent about the pointer example. It's not complete without it, as you can use pointers instead of references, but (as you say) it might be muddying the waters. I barely kept myself from making `swap` a template specialization.
outis
+1 for also explaining pointers
RobS
@RobS: you give me too much credit. If Sagistic isn't familiar with pointers, I might have to do it.
outis
@outis: Perhaps you could add a section below explaining pointers briefly? I do agree that it adds to your answer in that pointers is probably the next concept to tackle for the OP after references, so this could be a good step in that direction. However without at least a brief description of what pointers are, I think it simply adds unecessary complexity to what could otherwise be a more to-the-point example of the topic at hand. Plus a description could benefit others who are learning about the topic.
Cam
@incrediman: I tend to disagree with this. I guess this all boils down to the learning approach you want to take. While most people tend to start from the C-like subset of the language others (including me) prefer a different approach. The problem I see with learning C++ with pointers from day 1 is that you suddenly have to ways of doing similar things, and suddenly *when should I prefer one over the other* questions arise (search SO). Koening in *Accelerated C++* takes a different approach. The first half of the book does not use pointers or classes for about half of the book. I like that.
David Rodríguez - dribeas
BTW: a solution to the 'when should I use pointers over references (or viceversa)' problem is teaching only references. Then at one point (no pun intended) you will *need* pointers. Hopefully then the *references* will be sufficiently internalize that the student will default to references and only use pointers where needed.
David Rodríguez - dribeas