Previous Table of Contents Next


Chapter 8
C++ Traps and Pitfalls

by Andrew Koenig

8.1. Why Programmers Get in Trouble

This chapter describes ways in which C++ programmers are likely to go wrong. It assumes a knowledge of C++, and therefore of C, but it concentrates on problems that C++ programmers encounter and C programmers do not.

How do programmers get in trouble? Typically, a programmer writes a program, expecting it to behave in a particular way, only to find that it does something else. Except for compiler bugs, which are relatively rare, such program misbehavior ultimately comes from programmer misunderstanding. The program did what the programmer asked, but what the programmer asked is not what the programmer wanted.

As an analogy, imagine a hiking trail with natural hazards. If those hazards occur in obvious places, hikers avoid them. If not, the hazards may injure people. We might be able to remove some of the hazards, but removing others might change the whole character of the trail. If we cannot remove a hazard, we can still make the trail less dangerous by warning people about where the hazards are. The deliberately vague phrase “traps and pitfalls” suggests such a trail.

On the surface, the mere existence of this chapter might be surprising. If it is possible to predict how people are likely to misunderstand C++, why was C++ not designed to match people’s expectations in the first place and thereby avoid misunderstandings? A deeper look, however, reveals that all programming tools inevitably have pitfalls.

One reason that pitfalls are inevitable is that different people have different expectations. For example, consider the viewpoints implied by the questions, “If the compiler knows that I’m doing something wrong, why doesn’t it do the right thing automatically?” and “Why can’t the compiler do exactly what I say so that I know what is going on?” Any effort to fix the pitfalls that one of these questioners encounters is likely to cause trouble for the other one. Such differences of opinion are particularly prominent in large, diverse user communities.

Another reason for pitfalls is that people’s expectations change as they gain experience. Catering to people’s expectations changing as they learn is like putting training wheels on bicycles. At some point in most of our childhoods, we learn how to keep a bicycle upright, and we can put away the training wheels. Just as it does not always make sense to sell bicycles with training wheels already installed, it does not always make sense to design a programming language to cater to beginners in preference to experts.

A third reason that pitfalls are inevitable is that every useful language has users, and every user community has inertia. Users are accustomed to solving particular problems in a particular way. When new facilities enter the language, there is pressure to fit them into the same conceptual model as the facilities that already exist and to retain those existing facilities to let programmers continue to work in familiar ways. Once a part of a language is established, it is difficult to go back and change it later.

As an example of linguistic inertia, consider how hard it is to talk in English about a person without revealing that person’s gender. This difficulty is not unique to English, but neither is it universal. In French, for example, the gender of possessive pronouns is the gender of what is possessed, rather than the gender of the person possessing it. Few English speakers believe that it is an advantage for them to be forced to ascribe genders to unknown people, and it is sometimes clearly a substantial disadvantage to have to do so. It would be easy enough to solve this problem with the English language completely by agreeing on a few new pronouns that carry no gender implications, so why does English still have the problem? Inertia.

A fourth reason that pitfalls are inevitable is that a programming language is usually a compromise among several conflicting goals. For example, one purpose of C++ is to express programs that run efficiently with a minimum of special-purpose operating-system support. This purpose argues for a small interface between the language and the operating system. The desire for portability and operating-system independence is another argument for keeping the interface between C++ and the operating system small. Yet some entirely plausible applications of C++ undoubtedly need to rely on specific features of a specific operating system, such as threads or shared memory, and those applications would be easier to write if the language included direct support for those features—even at the cost of allowing the language to work only with operating systems that support those features. It is hard to imagine how to cater to such conflicting goals without creating problems somewhere.

Fortunately, people are usually good at learning how to avoid hazards, especially if there is someone around to show them where the hazards are. Hikers learn how to avoid injury, authors learn how to write gender-independent English prose, and programmers learn which approaches are likely to get them into trouble and which ones are likely to avoid it.

8.1.1. Two Sources of Pitfalls

There are typically two main reasons why programmers expect programs to behave differently from the way they actually behave.

One reason is a mistaken assumption. A programmer looks at part of a program and makes an obvious guess about how it works—but the guess is wrong. For example, a programmer coming to C++ from Fortran might assume that the last element of an n-element array has index n. That assumption is wrong because C++ arrays start from zero. The last element of an n-element array therefore has index n-1.

The other reason for incorrect expectations is that some parts of a system are just plain complicated, and a programmer who has not learned those parts of the system thoroughly will make mistakes. For example, most programmers realize that floating-point numbers are not completely accurate, but they do not understand all the implications of working with approximate numbers. This incomplete understanding is often harmless, but it can result in programs that fail in subtle ways—in any language.

As a result, the problems that this chapter describes are typically either simple or complicated with few problems between the extremes.


Previous Table of Contents Next