The default method for passing arguments in C++ is by value. That is, a copy of the argument is made:
Code:
#include <iostream>
using namespace std;
int f(int a)
{
return ++a;
}
int main()
{
int i = 0;
cout<< f(5) <<endl;
cout<< f(i) <<endl;
cout<< i <<endl;
}
The first key to understanding how this works is to realize that a is a separate variable from i, and it's local to f. The value of 5 is copied into a for the first call to f and the value of i (0) is copiled into a for the second call. The effect is equivalent to this:
Code:
#include <iostream>
using namespace std;
int main()
{
int i = 0;
int a;
a = 5;
cout<< ++a <<endl;
a = i;
cout<< ++a <<endl;
cout<< i <<endl;
}
Because any changes to a are completely separate from i, the value of i is never touched except to make the copy. Because of this, we can use literal values . If the original value could be modified then f(5) would be an error because you can't change the value of an integer literal. That's how passing by value works.
In C, the situation arises where you need to pass an object by reference. This means passing the object in such a way that any changes to a would be reflected in i. Naturally, because you can't change the value of a literal, f(5) would no longer work under this scheme.
However, because C doesn't support anything except passing by value, we need to simulate passing by reference by passing the address of i to f as a pointer.
Code:
#include <iostream>
using namespace std;
int f(int *a)
{
return ++(*a);
}
int main()
{
int i = 0;
cout<< f(&i) <<endl;
cout<< i <<endl;
}
This way, the original object (i) is accessable from within f. The address of i is still passed by value, but when the address is dereferenced, f has access to the actual value of i. Any changes to that value will be reflected by i when f returns. The equivalent behavior is:
Code:
#include <iostream>
using namespace std;
int main()
{
int i = 0;
cout<< ++i <<endl;
cout<< i <<endl;
}
This is how C can simulate passing by reference. The pointer is passed by value, but the address of the original object is still accessable. True passing by reference doesn't require this indirect sleight of hand because a true reference is a synonym for the original object. The two refer to the same thing, but are called different names.
Now you should see how the rule of thumb came about: If you want to modify an object by passing it to a function, pass a pointer to the object.
The nice thing about all of this is it's very regular. The rule applies for pointers too because pointers count as objects that hold addresses. The following does not work as expected:
Code:
#include <iostream>
using namespace std;
int i = 10;
int j = 20;
void f(int *a)
{
a = &j;
}
int main()
{
int *p = &i;
cout<< *p <<endl;
f(p);
cout<< *p <<endl;
}
Because a is a pointer to i, the value of i can be changed through a, but the address that p points to cannot be changed because a is a copy of the address. To re-assign p to j, you need to pass a pointer to the pointer to the function:
Code:
#include <iostream>
using namespace std;
int i = 10;
int j = 20;
void f(int **a)
{
*a = &j;
}
int main()
{
int *p = &i;
cout<< *p <<endl;
f(&p);
cout<< *p <<endl;
}
The same rule applies for a pointer to a pointer. If you want to dynamically allocate a two dimensional array of int (int **) from another function, you need to pass a pointer to int ** (int ***):
Code:
#include <iostream>
using namespace std;
void alloc2d(int ***mat)
{
*mat = new int*[5];
for (int i = 0; i < 5; i++)
(*mat)[i] = new int[5];
}
int main()
{
int **p;
alloc2d(&p);
}
The "obvious" solution:
Code:
#include <iostream>
using namespace std;
void alloc2d(int **mat)
{
mat = new int*[5];
for (int i = 0; i < 5; i++)
mat[i] = new int[5];
}
int main()
{
int **p;
alloc2d(p);
}
Wouldn't work for the same reason that a pointer passed as a pointer can't be re-assigned. To modify the original, you need a pointer to it. Fortunately, you'll be hard pressed to ever use more than single indirection in C++. In C you should be surprised to use more than triple indirection (though I have used more than that before, but only once).
Stepping away from the C way of doing things, C++ supports true references. To pass an integer by reference and have the changes reflected in main, you simply would do this:
Code:
#include <iostream>
using namespace std;
int f(int& a)
{
return ++a;
}
int main()
{
int i = 5;
cout<< f(i) <<endl;
cout<< i <<endl;
}
It's easier to get right because you don't have to play with multiple levels of indirection, and it's intuitive. To pass a pointer by reference so that you can allocate memory to it, you tack a & on the end of the type in your parameter list:
Code:
#include <iostream>
using namespace std;
void alloc(int*& a)
{
a = new int;
}
int main()
{
int *i;
alloc(i);
*i = 0;
}
Once again, the syntax and behavior are very regular. To allocate a two dimensional array, you take the "obvious" solution and pass a reference:
Code:
#include <iostream>
using namespace std;
void alloc2d(int**& mat)
{
mat = new int*[5];
for (int i = 0; i < 5; i++)
mat[i] = new int[5];
}
int main()
{
int **p;
alloc2d(p);
}
Now it's obvious and correct.
When using C++, you should try using references first, and then pointers if references don't work out. The pointer solution is a bit of a hack to work around the default argument passing scheme in C. Because C++ supports other schemes, the hack isn't always appropriate anymore.