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
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.
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++ programsavoiding both the C and C++ pitfallsis 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 problemsand for their solutions.
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.
Koenig, A. 1989. C traps and pitfalls. Reading, MA: Addison-Wesley.
Previous | Table of Contents | Next |