Professional C__ - Marc Gregoire [136]
Several overloaded operators commonly return references. Chapter 7 shows some examples, and you can read about more applications of this technique in Chapter 18.
Deciding between References and Pointers
References in C++ could be considered redundant: almost everything you can do with references, you can accomplish with pointers. For example, you could write the previously shown swap() function like this:
void swap(int* first, int* second)
{
int temp = *first;
*first = *second;
*second = temp;
}
However, this code is more cluttered than the version with references: References make your programs cleaner and easier to understand. References are also safer than pointers: It’s impossible to have an invalid reference, and you don’t explicitly dereference references, so you can’t encounter any of the dereferencing errors associated with pointers. These arguments, saying that references are safer, are only valid in the absence of any pointers. For example, take the following function which accepts a reference to an int:
void refcall(int& t) { ++t; }
You could declare a pointer and initialize it to the null pointer. Then you could dereference this pointer and pass it as the reference argument to refcall() as in the following code. This code will compile but will crash when trying to execute it.
int main()
{
int* ptr = nullptr;
refcall(*ptr);
return 0;
}
Most of the time, you can use references instead of pointers. References to objects even support polymorphism in the same way as pointers to objects. The only case in which you need to use a pointer is when you need to change the location to which it points. Recall that you cannot change the variable to which references refer. For example, when you dynamically allocate memory, you need to store a pointer to the result in a pointer rather than a reference.
Another way to distinguish between appropriate use of pointers and references in parameters and return types is to consider who owns the memory. If the code receiving the variable is responsible for releasing the memory associated with an object, it must receive a pointer to the object. If the code receiving the variable should not free the memory, it should receive a reference.
Use references instead of pointers unless you need to change to where the reference refers to.
Strict application of this rule can lead to some unfamiliar syntax. Consider a function that splits an array of ints into two arrays: one of even numbers and one of odd numbers. The function doesn’t know how many numbers in the source array will be even or odd, so it should dynamically allocate the memory for the destination arrays after examining the source array. It should also return the sizes of the two new arrays. Altogether, there are four items to return: pointers to the two new arrays and the sizes of the two new arrays. Obviously, you must use pass-by-reference. The canonical C way to write the function looks like this:
void separateOddsAndEvens(const int arr[], int size, int** odds,
int* numOdds, int** evens, int* numEvens)
{
// First pass to determine array sizes
*numOdds = *numEvens = 0;
for (int i = 0; i < size; i++) {
if (arr[i] % 2 == 1) {
(*numOdds)++;
} else {
(*numEvens)++;
}
}
// Allocate two new arrays of the appropriate size.
*odds = new int[*numOdds];
*evens = new int[*numEvens];
// Copy the odds and evens to the new arrays
int oddsPos = 0, evensPos = 0;
for (int i = 0; i < size; i++) {
if (arr[i] % 2 == 1) {
(*odds)[oddsPos++] = arr[i];
} else {
(*evens)[evensPos++] = arr[i];
}
}
}
Code snippet from References\OddsEvensPtrs.cpp
The final four parameters to the function are the “reference” parameters. In order to change the values to which they refer, separateOddsAndEvens() must dereference them, leading to some ugly syntax in the function body.
Additionally, when you want to call separateOddsAndEvens(), you must pass the address of two pointers so