[ Team LiB ] Previous Section Next Section

Gotcha #5: Misunderstanding References

There are two common problems with references. First, they're often confused with pointers. Second, they're underused. Many current uses of pointers in C++ are really C holdovers that should be ceded to references.

A reference is not a pointer. A reference is an alias for its initializer. Essentially, the only thing one can do with a reference is initialize it. After that, it's simply another way of referring to its initializer. (But see Gotcha #44.) A reference doesn't have an address, and it's even possible that it might not occupy any storage:



int a = 12; 


int &ra = a;


int *ip = &ra; // ip refers to a


a = 42; // ra == 42


For this reason, it's illegal to attempt to declare a reference to a reference, a pointer to a reference, or an array of references. (Though the C++ standards committee has discussed allowing references to references in the future, at least in some contexts.)



int &&rri = ra; // error! 


int &*pri; // error!


int &ar[3]; // error!


References can't be const or volatile, because aliases can't be const or volatile, though a reference can refer to an entity that is const or volatile. An attempt to declare a reference const or volatile directly is an error:



int &const cri = a; // should be an error . . . 


const int &rci = a; // OK


Strangely, it's not illegal to apply a const or volatile qualifier to a type name that is of reference type. Rather than cause an error, in this case the qualifier is ignored:



typedef int *PI; 


typedef int &RI;


const PI p = 0; // const pointer


const RI r = a; // just a reference!


There are no null references, and there are no references to void:



C *p = 0; // a null pointer 


C &rC = *p; // undefined behavior


extern void &rv; // error!


A reference is an alias, and an alias has to refer to something.

Note, however, that a reference does not have to refer to a simple variable name. It's sometimes convenient to bind a reference to an lvalue (see Gotcha #6) resulting from a more complex expression:



int &el = array[n-6][m-2]; 


el = el*n-3;


string &name = p->info[n].name;


if( name == "Joe" )


   process( name );


A reference return from a function allows assignment to the result of a call. The canonical example of this is an index function for an abstract array:

gotcha05/array.h



template <typename T, int n> 


class Array {


 public:


   T &operator [](int i)


       { return a_[i]; }


   const T &operator [](int i) const


       { return a_[i]; }


   // . . .


 private:


   T a_[n];


};


The reference return permits a natural syntax for assignment to an array element:



Array<int,12> ia; 


ia[3] = ia[0];


References may also be used to provide additional return values for functions:



Name *lookup( const string &id, Failure &reason ); 


// . . .


string ident;


// . . .


Failure reasonForFailure;


if( Name *n = lookup( ident, reasonForFailure ) ) {


   // lookup succeeded . . .


}


else {


   // lookup failed. check reason . . .


}


Casting an object to a reference type has a very different effect from the same cast to the nonreference version of the type:



char *cp = reinterpret_cast<char *>(a); 


reinterpret_cast<char *&>(a) = cp;


In the first case, we're converting an integer into a pointer. (We're using reinterpret_cast in preference to an old-style cast, like (char *)a. See Gotcha #40.) The result is a copy of the integer's value, interpreted as a pointer.

The second cast is very different. The result of the cast to reference type is a reinterpretation of the integer object itself as a pointer. It's an lvalue, and we can assign to it. (Whether we will then dump core is another story. Use of reinterpret_cast generally implies "not portable.") An analogous attempt with a cast to nonreference will fail, because the result of the cast is an rvalue, not an lvalue:



reinterpret_cast<char *>(a) = 0; // error! 


A reference to an array preserves the array bound. A pointer to an array does not:



int ary[12]; 


int *pary = ary; // point to first element


int (&rary)[12] = ary; // refer to whole array


int ary2[3][4];


int (*pary2)[4] = ary2; // point to first element


int (&rary2)[3][4] = ary2; // refer to whole array


This property can be of occasional use when passing arrays to functions. (See Gotcha #34.)

It's also possible to bind a reference to a function:



int f( double ); 


int (* const pf)(double) = f; // const pointer to function


int (&rf)(double) = f; // reference to function


There's not much practical difference between a constant pointer to function and a reference to function, except that the pointer can be explicitly dereferenced. As an alias, the reference cannot, although it can be converted implicitly into a pointer to function and then dereferenced:



a = pf( 12.3 ); // use pointer 


a = (*pf)(12.3); // use pointer


a = rf( 12.3 ); // use reference


a = f( 12.3 ); // use function


a = (*rf)(12.3); // convert ref to pointer and deref


a = (*f)(12.3); // convert func to pointer and deref


Distinguish references and pointers.

    [ Team LiB ] Previous Section Next Section