Previous | Table of Contents | Next |
Otherwise, it is necessary to write
t.setsize(12); t.setfont(Goudy);
Of course, the gain is relatively minor in this particular example. It is easier to see the advantage when writing statements such as
cout << x = << x << , y = << y << endl;
This example works only because ostream::operator<< returns *this as a reference. Each call to operator<< therefore passes its left argument as its result; that result becomes the left argument of the next call to operator<<.
The third common use for such functions is accessing elements of a container. For example, consider a class intended to act like a built-in array:
template<classT> class Array { public: Array(int); // ... T& operator[] (int); // ... };
Such a class supports usages such as
Array<int> x(20); // ... x[i] = 0;
There are three things to remember when writing functions that return references.
First, never return a reference to a local variable. The local variable disappears as soon as the function returns and the reference leads nowhere. Be sure that if you do return a reference, it refers to an object that will still be around after the function returns.
There is nothing wrong with returning a formal parameter as a reference, provided that the formal parameter is itself a reference:
// Dont do this int& f(int x) { return x; } // ...but this is fine int& g(int& x) { return x; }
The trouble with f is that the formal parameter x is essentially a local variable; it vanishes as soon as f returns. This is not a problem in g because here the parameter x is just an alternative name for something that existed before g was called and will presumably still exist after g has returned.
The second thing to remember is that whenever a function returns a reference, it is possible for the user of that function to convert the reference to a pointer, simply by taking the address of the object that the function returns. This may be hazardous for the same reason all pointer operations are:
main() { int* p; { Array<int> x[20]; p = &x[10]; } *p = 42; // Oops! }
Here, the Array<int> object x goes away while the pointer p still contains the address of one of its elements. This is, of course, the same hazard one must avoid when using built-in arrays.
The third pitfall is a subtle corollary to the second. Some containers may be implemented using self-adjusting data structures: data structures that move their contents around implicitly in response to user requests. A simple example of such a data structure is an array that grows automatically in response to attempts to refer to nonexistent elements.
Such containers must never yield references to their elements. Otherwise, they get into trouble if the compiler evaluates expressions that use them in the wrong order:
z[i] = z[j];
Suppose that z is of a type that automatically relocates its elements when necessary. Then it is possible for things to happen in this order:
The trouble comes when growing the container moves the object referred to by z[j]: That reference is now invalid. If you use references to access elements of a container, be careful about your choice of data structures.
Of course, a well-designed data structure that offers an indexing operation is careful about when that operation might conceivably relocate its components. In particular, the standard library data structures do take pains not to move their components around when doing so might crash straightforward user programs. The more care that experienced library authors take to do the right thing, however, the more important it becomes for programmers who are just starting to write libraries to be aware that there is a right thing to do.
The types built into C++ do not include multidimensional arrays. You allocate such arrays in C++ by allocating arrays of arrays. Programmers who want to allocate multidimensional arrays usually care the most about arrays with exactly two dimensions; higher dimensions are rarer. Nevertheless, if you can allocate a two-dimensional array, you can probably allocate a three- or four-dimensional one; the following discussion generalizes easily to any number of dimensions.
There is no particular problem in allocating arrays of arrays, as long as their sizes are known during compilation. The following discussion therefore concentrates on how to allocate arrays in dynamic memory.
Previous | Table of Contents | Next |