Item 5. References Are Aliases, Not Pointers
A reference is another name for an existing object. Once a reference is initialized with an object, either the object name or the reference name may be used to refer to the object.
int a = 12;
int &ra = a; // ra is another name for a
--ra; // a == 11
a = 10; // ra == 10
int *ip = &ra; // ip points to a
References are often confused with pointers, perhaps because C++ compilers often implement references as pointers, but they are not pointers and do not behave like pointers.
Three major differences between references and pointers are that there are no null references, all references require initialization, and a reference always refers to the object with which it is initialized. In the previous example, the reference ra will refer to a for its entire lifetime. Most erroneous uses of references stem from misunderstanding these differences.
Some compilers may catch an obvious attempt to create a null reference:
Employee &anEmployee = *static_cast<Employee*>(0); // error!
However, the compiler may not detect less obvious attempts to create a null reference, which will cause undefined behavior at runtime:
Employee *getAnEmployee();
//...
Employee &anEmployee = *getAnEmployee(); // probably bad code
if( &anEmployee == 0 ) // undefined behavior
If getAnEmployee returns a null pointer, then the behavior of this code is undefined. In this case, it's better to use a pointer to hold the result of getAnEmployee.
Employee *employee = getAnEmployee();
if( employee ) //...
The requirement that a reference must be initialized implies that the object to which it refers must be in existence when the reference is initialized. This is important, so I'll say it again: A reference is an alias for an object that already exists prior to the initialization of the reference. Once a reference is initialized to refer to a particular object, it cannot later be made to refer to a different object; a reference is bound to its initializer for its whole lifetime. In effect, after initialization a reference disappears and is simply another name for its initializer thereafter. This aliasing property is why references are often a good choice for function formal arguments; in the following swap template function, the formal arguments a and b become aliases for the actual arguments to the call:
template <typename T>
void swap( T &a, T &b ) {
T temp(a);
a = b;
b = temp;
}
//...
int x = 1, y = 2;
swap( x, y ); // x == 2, y == 1
In the call to swap above, a aliases x, and b aliases y, for the duration of the call. Note that the object to which a reference refers needn't have a name, so a reference may be used to give a convenient name to an unnamed object:
int grades[MAX];
//...
swap( grades[i], grades[j] );
After the formal arguments a and b of swap are initialized with the actual arguments grades[i] and grades[j], respectively, those two nameless array elements can be manipulated through the aliases a and b. This property may be used more directly in order to simplify and optimize.
Consider the following function that sets a particular element of a two-dimensional array:
inline void set_2d( float *a, int m, int i, int j ) {
a[i*m+j] = a[i*m+j] * a[i*m+i] + a[i*m+j]; // oops!
}
We can replace the line commented "oops!" with a simpler version that employs a reference and that has the additional advantage of being correct. (Did you catch the error? I didn't the first time around.)
inline void set_2d( float *a, int m, int i, int j ) {
float &r = a[i*m+j];
r = r * r + r;
}
A reference to a non-const cannot be initialized with a literal or temporary value.
double &d = 12.3; // error!
swap( std::string("Hello"), std::string(", World") ); // errors!
However, a reference to const can:
const double &cd = 12.3; // OK
template <typename T>
T add( const T &a, const T &b ) {
return a + b;
}
//...
const std::string &greeting
= add(std::string("Hello"),std::string(", World")); // OK
When a reference to const is initialized with a literal, the reference is set to refer to a temporary location that is initialized with the literal. Therefore, cd does not actually refer to the literal 12.3 but to a temporary of type double that has been initialized with 12.3. The reference greeting refers to the unnamed temporary string return value of the call to add. Ordinarily, such temporaries are destroyed (that is, go out of scope and have their destructors called) at the end of the expression in which they're created. However, when such a temporary is used to initialize a reference to const, the temporary will exist as long as the reference that refers to it.
|