[ Team LiB ] |
![]() ![]() |
Gotcha #5: Misunderstanding ReferencesThere 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:
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 ] |
![]() ![]() |