Related
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.
How and why would you want to use a reference? What would happen if you didn't make the parameter a reference, but instead left the & off?
For example, what's the difference between these functions:
int doSomething(int& a, int& b);
int doSomething(int a, int b);
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.
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 a value and the argument passed is passed 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. 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(nullptr, nullptr), swaps two nothings and dereferences null pointers...undefined behavior! Fixable with some checks:
void swap(int* x, int* y)
{
if (x == nullptr || y == nullptr)
return; // one is null; this is a meaningless operation
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, there are no null references.) 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 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 won'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.
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
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.
// 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.
A simple pair of examples which you can run online.
The first uses a normal function, and the second uses references:
Example 1 (no reference)
Example 2 (reference)
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
}
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.
How about by metaphor: Say your function counts beans in a jar. It needs the jar of beans and you need to know the result which can't be the return value (for any number of reasons). You could send it the jar and the variable value, but you'll never know if or what it changes the value to. Instead, you need to send it that variable via a return addressed envelope, so it can put the value in that and know it's written the result to the value at said address.
Correct me if I'm wrong, but a reference is only a dereferenced pointer, or?
The difference to a pointer is, that you can't easily commit a NULL.
I know this is already a commonly asked question, but I'm more curious about how pointers and references behave at a lower level (like how compiler deals with them, and how they look like in memory), and I didn't find a solution, so here I am.
At first I was wondering if an array can be passed as a parameter without being cast (or decay) into a pointer. More specifically. I would like the following code:
void func(?? arr) {
cout << sizeof(arr) << "\n";
}
int main() {
int arr[4];
func(arr);
return 0;
}
to output 16 instead of 8, which is the size of a pointer.
First I tried
void func(int arr[4]);
Hoping that specifying the size can keep the property of an array, but arr is still treated as a pointer.
Then I found something that worked:
void func(int (&arr)[4]);
But it confused me.
In the past I was under the impression that although pointers and references had different meanings, they had the same behavior when the code was actually executed.
I got that idea from my own experiments:
void swap(int* a, int* b) {
int c = *a;
*a = *b;
*b = c;
}
int main() {
int a = 3, b = 5;
swap(&a, &b);
}
and
void swap(int& a, int& b) {
int c = a;
a = b;
b = c;
}
int main() {
int a = 3, b = 5;
swap(a, b);
}
were compiled into the same assembly code, and so did
int main() {
int a = 3;
int& b = a;
b = 127;
return 0;
}
and
int main() {
int a = 3;
int* b = &a;
*b = 127;
return 0;
}
I turned off optimization and both g++ and clang++ showed this result.
Also my thought on the first experiment:
when thinking in terms of memory, swap should have its own stack frame and local variables. Having a in swap directly mapped to the a in main didn't make much sense to me. It was as if the a in main magically appeared in the stack frame of swap, where it shouldn't belong. So it didn't surprise me that it got compiled into the same assembly as the pointer version. Maybe the magic of reference was achieved by a pointer underneath the hood. But now I'm not sure.
So how does a compiler handle references and how do references look like in memory? Are they variable that occupies space? And how can I explain the result of my experiments and the array problem?
The old C idiom is, that arrays get turned into pointers to the array type, when a function is called. And that is mostly still true für C++.
So for the details of it, let's look at two functions taking an integer argument, first:
void f(int x) { x = 42; } // function called with x by value
void g(int& x) { x = 42; } // function called with x by reference
As f() is called by value, a copy of the argument is made, and the assignment x = 42; inside the function has effectively no effect for the caller of the function. But for g(), the assignment of x becomes visible to the caller:
int a = 0, b = 0; // initialize both a and b to zero
f(a); // a is not changed, because f is called by value
g(b); // b is changed, because g is called by reference
std::cout << a << " " << b << std::endl; // prints 0 42
For arrays, the same rules should hold, but don't. So let's try it:
void farr(int x[4]) { x[0] = 42; } // hypothetical call by value
void garr(int (&x)[4]) { x[0] = 42; } // call by reference
And let's call these functions:
int c[4] = { 1, 2, 3, 4 }, d[4] = { 5, 6, 7, 8 };
farr(c); // call by value?
garr(d); // call by reference
for(unsigned i = 0; i < 4; i++)
std::cout << c[i] << (i < 3 ? ", " : "\n");
for(unsigned i = 0; i < 4; i++)
std::cout << d[i] << (i < 3 ? ", " : "\n");
The unexpected result is, that the farr() function (unlike the f function before) does modify its array, too. The reason is an old C idiom: Because arrays must not be copied directly by assignment, functions cannot be called with arrays by value. Therefore array declarations in parameter lists are automatically converted into a pointer to the first element of the array. So the following four function declarations are syntactically identical in C:
void farr1(int a[]) {}
void farr2(int a[4]) {}
void farr3(int a[40]) {}
void farr4(int *a) {}
Being compatible with C, C++ took over that property. So it is not a syntactical error (but likely causes undefined behaviour!) to call farr2() or farr3() with an array of different size. Furthermore, though, references come in in C++. And yes, as you already suspected, a reference to an array is internally represented as a pointer to the first array element. But, and that is the advantage of C++: If you call a function, that expects a reference to an array (and not just an array or a pointer), the size of array is actually validated!
So, calling farr() with an int-Array of size 5 is possible, calling garr() with the same leads to an compiler error. That gives you better type checking, so you should use it, whereever possible. And it even allows you to pass the array size to a function by using a template:
template<std::size_t N>
void harr(int (&x)[N]) { for(std::size_t i = 0; i < N; i++) x[i] = i*i; }
From another post, I test the example regarding const X& x in function. This link What does “const X& x” mean? says "It means x aliases an X object, but you can’t change that X object via x." But below example shows we can modify X object? Why?
#include <iostream>
using namespace std;
int a = 1;
void DoWork(const int &n)
{
a = n * 2; // If n was a reference to a, n will have been doubled
cout << "n: " << n << endl;
//f(); // Might change the value of whatever n refers to
}
int main()
{
DoWork(a);
cout << "a: " << a << endl;
}
Follow up question:
For example, we have a function return const reference, why can it be assigned to non-const reference?
const X& tmp = get();
X& other = tmp; //this works, but why? this get rids of const X& in tmp?
I think you have confusion about const and references.
Firstly what is reference `
it's another name(alias) for an object.
About what's written in function parameters means that n is a const reference. It means you can't change a through n.(or in the simple sense, n can't be on the left side of the assignment operator. It also means that n is bounded to the object permanently.
n = n+2; // This is an error as n is const and hence you can't use n to modify the object it is pointing.
a = n+2; // here you are not using `n` to modify. So this is ok.
int x = 10 ;
n = x; // error as n is bounded to `a` so it can't be bounded again to any other variable x. You can see `n` is on left which is an error.
Now if you do this
cout<<a<<"\t"<<n<<endl; // Output 3; (1+2)
About follow up question. (Read it carefully)
Let us suppose a general scenario.
const int ci = 1024;
int &r2 = ci; // error: non const reference to a const object
The same is the case with your function question. You must be wondering why we can't do such a thing.
See here c1 is const so we can't change it. Now suppose(JUST SUPPOSE) the following code would have been a legal code. Then r2 will be a reference to c1.
Now, As i stated reference is other name of an object. It means if we do arithmetic on r2 it will be done on ci.
r = r+2; // it means ci = ci + 2 ;
but see ci was const(it shouldn't be altered but we altered it.)
So we need to do something like.
const int ci = 1024;
const int &r2 = ci; // error: non-const reference to a const object
This ensures that we can't do
r = r +2; which ensures/preserves the constness of ci.
get() is returning const so to preserve it's constness the variable used to receive from function must be const. So, the part which you said is working is an error as per standard, may be it's your compiler extension.
Keep in mind that const X& essentially means that the compiler imposes restriction that you cannot mutate the value reference via the reference (as the page you linked says). The actual value referenced by the reference is purely determined at runtime and the compiler cannot infer that you are mutating the value referenced by x when it sees the line a = n * 2;.
A small change to your code can help what is going on,
#include <iostream>
using namespace std;
int a = 1;
int b = 1; // new variable
void DoWork(const int &n)
{
a = n * 2; // If n was a reference to a, n will have been doubled
cout << "n: " << n << endl;
//f(); // Might change the value of whatever n refers to
}
int main()
{
// Whether `x` in `DoWork` refers to `b` or `a` is purely determined at runtime.
if (rand() % 2 == 0)
DoWork(a);
else
DoWork(b);
cout << "a: " << a << endl;
}
In other words, the compiler cannot be sure of what values references will point to. In C++, references can even point to invalid memory,
int *a = 0;
int &ref = *a;
cout << ref; // runtime error
Hope this helps.
Edit:
Regarding the followup question, what compiler are you using? Any standard compiler would refuse to compile the code.
I'm was playing around with C++(It's been over 10 years since I used the language) and wrote the the small program below and was a bit surprised by the result:
#include <iostream>
int& getReference()
{
int x = 33;
return x;
}
int main()
{
auto a = getReference() = 4;
auto b = getReference();
std::cout << "a: " << a << " b: " << b;
return 0;
}
The output is "a: 4 c: 33" when I was expecting "a: 33 c: 33".
The reason I was expecting "a" to equal 33 is because i'm first setting the getReference lvalue return reference to 4 and than "return x" is overriding that lvalue reference with 33.
Anyway, I thought maybe can explain where I'm wrong here?
Thanks for everyone's help!
Edit 1
So many responses really quick. Thanks everyone for your help!
Edit 2 I'm not sure if it's too late to add this or I should make it a new question. I changed "x" as a global variable and got the same answer. So i'm now still confused because the global variable x didn't go away.
Your getReference returns a reference to a local variable, so by the time the function returns, it's a dangling reference (i.e., the variable to which it referred has been destroyed). Any use of that reference (reading or writing) causes undefined behavior.
So, basically, you've broken the contract with the compiler in a way that allows it to do anything. You have no right to be surprised at any result.
If you change it to a global:
#include <iostream>
int x;
int& getReference()
{
x = 33;
return x;
}
int main()
{
auto a = getReference() = 4;
auto b = getReference();
std::cout << "a: " << a << " b: " << b;
return 0;
}
Then you should expect a: 4 b: 33. The first invocation retrieves a reference to x, then assigns 4 to the referent, than retrieves 4 from the referent and assigns it to a. The second invocation assigns 33 to the referent, then retrieves the 33 from the referent and assigns it to b.
If you changed a and b to references:
// ...
auto &a = getReference() = 4;
auto &b = getReference();
// remainder unchanged
Then you should see a:33 b:33. In this case, both a and b refer to the original variable. Since getReference assigns 33 after the 4 gets assigned, the value 4 is overwritten with 33.
In other words, in this case both a and b are just references to the single global, so the overall effect is pretty much the same as if you had:
int x;
int main(){
x = 33;
x = 4;
x = 33;
std::cout << x << " " << x << '\n';
}
A function should not return a reference to a local variable. The problem is that you're returning a reference to x, but x goes out of scope at the end of the function. As soon as it goes out of scope, any references to it are invalid and dereferencing them invokes undefined behavior.
If that were fixed, and you returned a reference to a globally-scoped variable instead, you would still see the same behavior, because:
auto a = getReference() = 4;
The right-hand expression is evaluated first:
getReference() = 4
This assigns 4 to the reference returned by the function. Then, the result of that expression (4) is assigned to a.
auto b = getReference();
Here, getReference() is evaluated. As you pointed out, the function resets the value of the variable it returns a reference to. So the result of the expression is 33 which gets assigned to b.
That code is undefined behavior, you are returning a reference to a local object. Any use of the reference will be undefined behavior. Now, assuming that the reference returned was to an object with a longer life span...
auto a = getReference() = 4;
The order of operations is equivalent to the parenthesized expresssion:
auto a = (getReference() = 4);
That is, you get a reference to an object (hopefully alive!) update that object to hold the value 4, then assign the result of that to the newly created a.
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.
How and why would you want to use a reference? What would happen if you didn't make the parameter a reference, but instead left the & off?
For example, what's the difference between these functions:
int doSomething(int& a, int& b);
int doSomething(int a, int b);
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.
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 a value and the argument passed is passed 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. 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(nullptr, nullptr), swaps two nothings and dereferences null pointers...undefined behavior! Fixable with some checks:
void swap(int* x, int* y)
{
if (x == nullptr || y == nullptr)
return; // one is null; this is a meaningless operation
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, there are no null references.) 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 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 won'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.
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
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.
// 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.
A simple pair of examples which you can run online.
The first uses a normal function, and the second uses references:
Example 1 (no reference)
Example 2 (reference)
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
}
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.
How about by metaphor: Say your function counts beans in a jar. It needs the jar of beans and you need to know the result which can't be the return value (for any number of reasons). You could send it the jar and the variable value, but you'll never know if or what it changes the value to. Instead, you need to send it that variable via a return addressed envelope, so it can put the value in that and know it's written the result to the value at said address.
Correct me if I'm wrong, but a reference is only a dereferenced pointer, or?
The difference to a pointer is, that you can't easily commit a NULL.