MFC Programmer's SourceBook : Thinking in C++
Bruce Eckel's Thinking in C++, 2nd Ed Contents | Prev | Next

References in C++

A reference (&) is like a constant pointer that is automatically dereferenced. It is usually used for function argument lists and function return values. But you can also make a free-standing reference. For example,

int x;
int & r = x; 
When a reference is created, it must be initialized to a live object. However, you can also say

int & q = 12;

Here, the compiler allocates a piece of storage, initializes it with the value 12, and ties the reference to that piece of storage. The point is that any reference must be tied to someone else’s piece of storage. When you access a reference, you’re accessing that storage. Thus if you say,

int x = 0;
int & a = x;
a++;
incrementing a is actually incrementing x. Again, the easiest way to think about a reference is as a fancy pointer. One advantage of this pointer is you never have to wonder whether it’s been initialized (the compiler enforces it) and how to dereference it (the compiler does it).

There are certain rules when using references:

  1. A reference must be initialized when it is created. (Pointers can be initialized at any time.)
  2. Once a reference is initialized to an object, it cannot be changed to refer to another object. (Pointers can be pointed to another object at any time.)
  3. You cannot have NULL reference
s. You must always be able to assume that a reference is connected to a legitimate piece of storage.

References in functions

The most common place you’ll see references is in function arguments and return values. When a reference is used as a function argument, any modification to the reference inside the function will cause changes to the argument outside the function. Of course, you could do the same thing by passing a pointer, but a reference has much cleaner syntax. (You can think of a reference as nothing more than a syntax convenience, if you want.)

If you return a reference from a function, you must take the same care as if you return a pointer from a function. Whatever the reference is connected to shouldn’t go away when the function returns; otherwise you’ll be referring to unknown memory.

Here’s an example:

//: C11:Reference.cpp
// Simple C++ references

int* f(int* x) {
  (*x)++;
  return x; // Safe; x is outside this scope
}

int& g(int& x) {
  x++; // Same effect as in f()
  return x; // Safe; outside this scope
}

int& h() {
  int q;
//!  return q;  // Error
  static int x;
  return x; // Safe; x lives outside scope
}

int main() {
  int a = 0;
  f(&a); // Ugly (but explicit)
  g(a);  // Clean (but hidden)
} ///:~ 

The call to f( ) doesn’t have the convenience and cleanliness of using references, but it’s clear that an address is being passed. In the call to g( ), an address is being passed (via a reference), but you don’t see it.

const references

The reference argument in Reference.cpp works only when the argument is a non- const object. If it is a const object, the function g( ) will not accept the argument, which is actually a good thing, because the function does modify the outside argument. If you know the function will respect the constness of an object, making the argument a const reference will allow the function to be used in all situations. This means that, for built-in types, the function will not modify the argument, and for user-defined types the function will call only const member functions, and won’t modify any public data members.

The use of const references in function arguments is especially important because your function may receive a temporary object, created as a return value of another function or explicitly by the user of your function. Temporary objects are always const, so if you don’t use a const reference, that argument won’t be accepted by the compiler. As a very simple example,

//: C11:Pasconst.cpp
// Passing references as const

void f(int&) {}
void g(const int&) {}

int main() {
//!  f(1); // Error
  g(1);
} ///:~ 

The call to f(1) produces a compiler error because the compiler must first create a reference. It does so by allocating storage for an int, initializing it to one and producing the address to bind to the reference. The storage must be a const because changing it would make no sense – you can never get your hands on it again. With all temporary objects you must make the same assumption, that they’re inaccessible. It’s valuable for the compiler to tell you when you’re changing such data because the result would be lost information.

Pointer references

In C, if you wanted to modify the contents of the pointer rather than what it points to, your function declaration would look like

void f(int**);

and you’d have to take the address of the pointer when passing it in:

int i = 47;
int* ip = &i;
f(&ip);
With references in C++, the syntax is cleaner. The function argument becomes a reference to a pointer, and you no longer have to take the address of that pointer. Thus,

//: C11:Refptr.cpp
// Reference to pointer
#include <iostream>
using namespace std;

void increment(int*& i) { i++; }

int main() {
  int* i = 0;
  cout << "i = " << i << endl;
  increment(i);
  cout << "i = " << i << endl;
} ///:~ 

By running this program, you’ll prove to yourself that the pointer itself is incremented, not what it points to.

Argument-passing guidelines

Your normal habit when passing an argument to a function should be to pass by const reference. Although this may at first seem like only an efficiency concern (and you normally don’t want to concern yourself with efficiency tuning while you’re designing and assembling your program), there’s more at stake: as you’ll see in the remainder of the chapter, a copy-constructor is required to pass an object by value, and this isn’t always available.

The efficiency savings can be substantial for such a simple habit: to pass an argument by value requires a constructor and destructor call, but if you’re not going to modify the argument then passing by const reference only needs an address pushed on the stack.

In fact, virtually the only time passing an address isn’t preferable is when you’re going to do such damage to an object that passing by value is the only safe approach (rather than modifying the outside object, something the caller doesn’t usually expect). This is the subject of the next section.

Contents | Prev | Next


Go to CodeGuru.com
Contact: webmaster@codeguru.com
© Copyright 1997-1999 CodeGuru