Previous Table of Contents Next


8.4.3.2. Pointers to Local Variables

There are a lot of cases where pointers address things that were not allocated by new. Even if we did not use new, we can still get into trouble:

   main()
   {
        int* p;
        {
             int x;
             p = &x;
        }
        *p = 42;              // #!?@#%^
   }

The problem here is the same as previously: We are assigning to *p after p no longer points anywhere useful.

Note that this example includes an inner block in which x is created and destroyed. Indeed, it is fairly difficult to create a dangling pointer without using delete or an inner block. The reason is that local variables are usually destroyed in reverse order of creation. To destroy an object while still retaining a pointer to it, it is therefore necessary to create the pointer before creating the object. Because the object does not exist when creating the pointer, we must give the pointer some other value and then change the value to refer to the object. The inner block is necessary to make is possible to use the pointer after the object has been destroyed.

In effect, our general rule has a corollary when dealing with local variables: When giving a value to a local pointer variable, make sure it’s the address of something that existed when the pointer was created. That ensures that the pointer is destroyed first.

Most of the time it is obvious when an assignment might violate this rule. For example, it is always safe to copy an old pointer into a new one:

   Thing* p;
   // ...
        {
                  Thing* q = p;
                  // ...
        }

because the new pointer is destroyed before the old one. Copying a new pointer into a old one is potentially dangerous, but the hazard exists only when the new pointer addresses an object created after the old pointer:

   Thing* p;
   // ...
        {
                  Thing* q;
                  // ...
                  p = q;            // potentially dangerous
        }

Here, the assignment p=q is dangerous if q refers to an object created more recently than p itself. Of course, if q refers to an object more recent than q, it is dangerous even without assigning q to p.

8.4.3.3. Pointers and Functions

Most of the time, these hazards are obvious upon inspection. One case, however, deserves special attention: returning a pointer from a function. This is a special case because the act of returning from a function destroys the function’s local variables while preserving the return value. If that return value points to a local variable, there’s trouble:

   int* fun()
   {
        int x = 42;
        return &x;  // error
   }

Of course, if a function accepts a pointer as an argument, it’s safe to return that pointer as a result.

8.4.4. References and Aliasing

Here is an implementation of the standard library find function:

   template<class It, class X>
   It find(It begin, It end, const X& x)
   {
        while (begin != end && *begin != x)
             ++begin;
        return begin;
   }

The types It and X are both template parameters. In principle, they can be arbitrary types. Yet the arguments of type It are passed by value and the argument of type X is passed by reference. Why?

One might be tempted at first to say that objects of type X are likely to be larger than objects of type It. After all, in many common uses of this function, type It is just a pointer, where type X might be a floating-point number, string, or other “large” type. It is therefore no more efficient to copy an object of type It than a pointer to it, but it might be more efficient to avoid copying an object of type X.

These arguments are valid, as far as they go, but they do not capture the whole truth. There are other reasons to use references or not to use them.

8.4.4.1. Aliasing and Reasons for Using References

We begin with the obvious observation that calling a function ordinarily copies the arguments into the parameters—unless the parameter type is a reference. That implies that a parameter whose type is not a reference must be capable of being copied.

There is no corresponding requirement on the type of a parameter that is a reference. That means that type X in the find function does not need to be copyable. It is perfectly acceptable for the third argument to find to be of a type that prohibits copying outright, as long as the type supports the != operation. Of course, for that to work, the != operation itself must be defined to take a reference parameter, rather than copy its argument.

Now what about the first parameter? It can’t be a reference to a constant because the function modifies it. Of course, we can change the function to copy it explicitly:

   template<class It, class X>
   It find (const It& begin, It end, const X& x)
   {
        It b = begin;
        while (b != end && *b != x)
             ++b;
        return begin;
   }

but what is the point of using a reference to avoid copying something that we immediately copy anyway? We could also make the first parameter a plain reference, but only at the cost of having the function destroy the value of the corresponding argument.

What about the second parameter? Here there is no obvious reason that it could not be made a reference. One practical reason it is not a reference is for consistency with the first parameter. Another is that this is one of those cases in which copying the argument is likely to be more efficient than not copying it because it avoids the extra memory reference associated with following a pointer each time begin and end are compared.


Previous Table of Contents Next