Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Professional C++ [eng].pdf
Скачиваний:
284
Добавлен:
16.08.2013
Размер:
11.09 Mб
Скачать

Understanding C++ Quirks and Oddities

Pass-by-Reference versus Pass-by-Value

Pass-by-reference is required when you want to modify the parameter and see those changes reflected in the variable argument to the function or method However, you should not limit your use of pass-by- reference to only those cases. Pass-by-reference avoids copying the argument to the function, providing two additional benefits in some cases:

1.Efficiency: large objects and structs could take a long time to copy. Pass-by-reference passes only a pointer to the object or struct into the function.

2.Correctness: not all objects allow pass-by-value. Even those that do allow it might not support deep copying correctly. As you learned in Chapter 9, objects with dynamically allocated memory must provide a custom copy constructor in order to support deep copying.

If you want to leverage these benefits, but do not want to allow the original objects to be modified, you can mark the parameters const. This topic is covered in detail later in this chapter.

These benefits to pass-by-reference imply that you should use pass-by-value only for simple built-in types like int and double for which you don’t need to modify the arguments. Use pass-by-reference in all other cases.

Reference Return Values

You can also return a reference from a function or method. The main reason to do so is efficiency. Instead of returning a whole object, return a reference to the object to avoid copying it unnecessarily. Of course, you can only use this technique if the object in question will continue to exist following the function termination.

Never return a reference to a variable, such as an automatically allocated variable on the stack, that will be destroyed when the function ends.

A second reason to return a reference is if you want to be able to assign to the return value directly as an lvalue (the left-hand side of an assignment statement).

Several overloaded operators commonly return references. You saw some examples in Chapter 9, and can read about more applications of this technique in Chapter 16.

Deciding between References and Pointers

References in C++ are mostly superfluous: 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;

}

327

Chapter 12

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. 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 dynamically allocate memory or otherwise change, or free, the value to which the pointer points.

This rule applies to stand-alone variables, function or method parameters, and function or method return values.

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)

{

int i;

// First pass to determine array sizes *numOdds = *numEvens = 0;

for (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];

328

Understanding C++ Quirks and Oddities

// Copy the odds and evens to the new arrays int oddsPos = 0, evensPos = 0;

for (i = 0; i < size; i++) { if (arr[i] % 2 == 1) {

(*odds)[oddsPos++] = arr[i];

} else {

(*evens)[evensPos++] = arr[i];

}

}

}

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 that the the function can change the actual pointers, and the address of two ints so that the function can change the actual ints:

int unSplit[10] = {1, 2, 3, 4, 5, 6, 6, 8, 9, 10}; int *oddNums, *evenNums;

int numOdds, numEvens;

separateOddsAndEvens(unSplit, 10, &oddNums, &numOdds, &evenNums, &numEvens);

If such syntax annoys you (which it should), you can write the same function using references to obtain true pass-by-reference semantics:

void separateOddsAndEvens(const int arr[], int size, int*& odds, int& numOdds,

int*& evens, int& numEvens)

{

int i;

numOdds = numEvens = 0;

for (i = 0; i < size; i++) { if (arr[i] % 2 == 1) { numOdds++;

} else { numEvens++;

}

}

odds = new int[numOdds];

evens = new int[numEvens];

int oddsPos = 0, evensPos = 0; for (i = 0; i < size; i++) { if (arr[i] % 2 == 1) {

odds[oddsPos++] = arr[i]; } else {

evens[evensPos++] = arr[i];

}

}

}

329