Previous Table of Contents Next


8.4.4.2. Aliases

In a slightly different context, there is yet another reason to avoid references. Consider this function, which multiplies every element of an array by a given constant:

   void scale(double* p, int n, const double& x)
   {
        for (int i = 0; i < n; ++i)
             p[i] *=x;
   }

Unless you were reading unusually carefully, you probably did not notice the misstatement in the description of the function. To see it, consider this example:

   double d[3] = { 2.0, 4.0, 6.0 };
   scale(d, 3, d[1]);

The value of d[1] is 4.0, so after calling scale, the elements of d should be 8.0, 16.0, and 24.0, right? Wrong. Because the third parameter to scale is a reference, it is fetched each time through the loop. After the second iteration, d[1] has been set to 16.0, so x is now bound to an object with value 16.0. This, in turn, causes d[2] to be set to 96.0, not 24.0.

This phenomenon is called aliasing. It comes about when a pointer or reference is bound to a component of a data structure that is potentially modified while the pointer or reference is active. In this particular example, the aliasing comes from the possibility that x might be bound to an element of the array that has p pointing to an element of it. Although x is a reference to const, that is no bar to the value being changed through a different access path, such as by assignment to p[i].

Of course, the way to avoid this problem is to ensure that x is a copy of its argument, rather than a reference to it:

   void scale(double* p, int n, double x)
   {
        for (int i = 0; i < n; ++i)
             p[i] *= x;
   }

8.4.4.3. Referencing and Aliasing Summary

Aside from the obvious efficiency considerations, the reasons to use or not to use references as function parameters include

  A parameter of reference type is not copied, which means it can potentially be of a type that does not support copying at all.
  There is little to be gained from avoiding copying a value that is going to be changed—unless one is sure that the original value is expendable.
  If a parameter is to be used many times, it may be more efficient to copy it than to follow a reference each time it is used.
  References can cause aliasing.

Efficiency is often important, of course, but it is not the only thing that influences design. Every use of a pointer or reference implies that the object pointed or referred to can potentially be fetched more than once; at times, that might not be easy to predict in advance. This might or might not be a problem. As ever, the way to avoid difficulties is to be aware of them.

8.5. Conclusion

C++ is based on C, so it has many of the same pitfalls. C++ offers facilities that C does not offer, so C++ has additional pitfalls that C does not have. Nevertheless, many experienced C++ programmers find that the total effort needed to write reliable C++ programs—avoiding both the C and C++ pitfalls—is less than it is to write corresponding programs in C. How can such things be?

The most important reason is that C++ supports abstractions better than C does. For example, a C program that does any significant string processing must take memory allocation into account almost every time it touches a string. A well-written C++ version of the same program defines a class that captures the desired properties of strings (or uses an appropriate library class that already exists) and then worries about memory allocation only once, in that class. In other words, although most of the C pitfalls still exist in C++, they are much less important because programmers can avoid them most of the time by using the C++ abstraction facilities.

Of course, those abstraction facilities introduce their own pitfalls. Those pitfalls, however, are often of a different nature from their C counterparts. Instead of a sharply defined hazard to avoid, the typical C++ pitfall is often a body of knowledge to understand. Understand it poorly, and the pitfall is always there. Understand it well, and the pitfall goes away.

The most obvious example of this kind of pitfall is the difference between assignment and initialization. Experience shows that beginning C++ programmers have more trouble with the distinction than with any other part of the language. Yet once a beginner understands the distinction completely, it ceases to be a problem.

Similar pitfalls exist in C as well. However, they tend to come from using libraries, rather than from the language itself. Libraries in any language offer higher-level abstractions than the language itself offers, which means that problems that arise when using libraries in any language are likely to come from understanding those abstractions incompletely.

Where C++ is unusual is in the range of abstractions that it offers. Accordingly, the corresponding pitfalls also cover a wide range. This chapter therefore included both high- and low-level pitfalls but did not attempt to be exhaustive. Instead, it gave a sample of problems that C++ programmers encounter in practice, and, perhaps, even gave a feel for where to look for other potential problems—and for their solutions.

8.6. Acknowledgments

This chapter is a revision of articles collected from an ongoing series in C++ Report magazine, published by SIGS Publications. Thanks to Barbara Moo and Bjarne Stroustrup for their many helpful comments on earlier drafts of this chapter.

8.7. Reference

Koenig, A. 1989. C traps and pitfalls. Reading, MA: Addison-Wesley.


Previous Table of Contents Next