OO Concept: Pointers & References

Pointers and references are not part of OO, languages like JAVA don't have pointers. However, they are so fundamental to the way C++ supports OO concepts that they deserve a topic.

Object, have data members, and so, like built-in data types, occupy memory. In order to access any data type its necessary to know whereabouts in memory it is. The compiler and linker can decide addresses for anything defined at compile time. However OO programs are dynamic, with objects being created at execution time. Then, in order to access them, its necessary to go via some type of pointer that holds the memory address. C++ has two types: Pointers and References.

Pointers

Pointers hold addresses of other data types. The address operator & is used to form an address and the dereference operator * is used to follow the address. For example:-

Int_t   MyInt = 2;
Int_t  *MyIntPtr = &MyInt;
*MyIntPtr = 4;
MyIntPtr is a pointer to MyInt. By dereferencing the pointer MyInt is retrieved. In this example it is then assigned to so MyInt receives the value 4.

Pointers know the size of the data type they point to. Where they point to arrays of the same type, the increment operator ++ and the decrement operator -- step the address by this size. This can be used to sequentially process arrays using the famously cryptic:-

*out++ = *in++;
The operator precedence rules tell us that on each side of the expression ++ takes priority over *, but, as it appears after the identifier, the value is that before incrementing. The net effect is shown by the diagram:-

- the contents from the in pointer is moved to the out pointer and both pointers step onto the next array element. If this command is repeated in a loop, sections of the array can be moved. Whether you regard the line of code as elegant or appalling tells something about how used to C++ you are! It certainly appears to some to violate the KISS (Keep It Simple Stupid) rule, while others would say it use is so common and natural as to make it simple.

References

A reference is simply an alternative identifier for a variable or object Its rather like the FORTRAN EQUIVALENCE, except that it can be defined at execution time. A reference is a type of pointer and when it is declared it must be initialised with the variable it is a reference to, for example:-

Int_t  &MyInt = YourInt;
This makes MyInt a reference to YourInt. From now on the two identifiers are equivalent. Expressions such as:-
YourInt = 7;
are identical to:-
MyInt = 7;
Behinds the scenes, the compiler knows that it only has a pointer and has to do some dereferencing, but this is all automatic - it better to forget about this trickery and just think of it as an alternative name. Once defined, a reference cannot be reassigned a new value; any attempt to modify it is interpreted as an attempt to modify the variable its a reference to, and trying to store a new address:-
YourInt = &HerInt;
is rejected by the compiler as an attempt to store a pointer in a non-pointer variable.

Using Pointers and References in Function Calls

References cannot do anything that pointers cannot do, however, they can make some code a lot clearer by removing lots of explicit dereferences. This is particularly true when passing modifiable arguments to a function. Consider this function:-
void ChangeIt(int num) {
  num = 5;
}
and what happens if we do:-
int MyNum = 1;
ChangeIt(MyNum);
cout << "MyNum = " << MyNum << endl;
A FORTRAN programmer could be forgiven for thinking that MyNum is now 5 because FORTRAN copies arguments by reference, but C++ copies arguments by value - changes within a function are not passed back. ChangeIt can change num as much as it likes, its just a local copy that changes and it won't be returned. If a function is to change its arguments then one way to do it is:-
void ChangeIt(int *num) {
  *num = 5;
}

int MyNum = 1;
ChangeIt(&MyNum);
cout << "MyNum = " << MyNum << endl;
Now we pass the address of MyNum. ChangeIt has a pointer with which to modify MyNum. It works, but its ugly, littering the code with & and *, but C++ offers a neater alternative:-
void ChangeIt(int &num) {
  num = 5;
}

int MyNum = 1;
ChangeIt(MyNum);
cout << "MyNum = " << MyNum << endl;
num is declared to be a reference, so when MyNum is passed its address is used to initialise num, which now just becomes just another name for MyInt. So argument passing acts as in FORTRAN.

Not only does argument passing this way make the code easier to read, it can save a lot of time too. As we have seen, the default is to copy by value, and this applies regardless of whether its just one byte or some enormous object. C++ is quite prepared to call a copy of an object into existence just for the function call. By making functions pass by reference avoids this and leads to another golden rule Always pass objects to functions as pointers or references never by value.

Functions can return pointers and references and these can then be used on the left hand size of an assignment statement. Consider:-

int& HisNum() {
  int num;
  return num;
}
this function returns an address as a reference which can then be assigned to:-
HisNum() = 6;
The HisNum function is called and returns an address which is used as the initialisation value of an unnamed reference to int, which is now just another name for num that is then assigned to. Actually this example has a dreadful error: num is a local variable that ceases to exist once the function exits so the assignment is writing to a bad memory address. When writing functions that return address and references, be very careful that the address remains valid once the function has returned.
Go Back to the The C++ Crib Top Page


If you have any comments about this page please send them to Nick West