Previous Table of Contents Next


8.4.2. Input/Output and Order of Evaluation

Suppose we have a variable named x and we want to use the standard C++ library to print its value with an appropriate label. Then we can say something like

   cout << “x = “ << x << endl;

and be confident that the three values that we are printing will appear in the order we requested: First the string x =, then the value of x, and finally a newline.

Suppose next that p is a pointer to an element of an array and we want to print the value of that element and the next element of that array. Similarly to before, we can say

   cout << *p << “, “ << *(p+1) << endl;

or, equivalently,

   cout << p[0] << “, “ << p[1] << endl;

It is therefore tempting to assume that we can also say

   cout << *p++ << “, “ << *p << endl;

but, perhaps surprisingly, that doesn’t work.

One way to understand this surprise is as a consequence of two rules:

  The implementation is permitted to evaluate the arguments to a function or operator in any order.
  The implementation must necessarily evaluate all the arguments to a function before calling the function itself.

These two rules seem straightforward enough: The first one should be well known to all C and C++ programmers and the second applies to any call-by-value language. Let’s see how they might apply to the previous expressions.

Note first that when we write an expression such as

   cout << “x = “ << x << endl;

the << operator is left associative. In other words, that expression is equivalent to

   ((cout << “x = “) << x) << endl;

We can view this expression as applying the << operator to two operands, the second of which is endl and the first of which is complicated. That application, in turn, has one of two possible meanings, depending on whether the particular operator<< being used is a member of the class of cout or not. Specifically, the expression above is equivalent either to

   operator<<(((cout << “x = “) << x), endl);

or to

   ((cout << “x = “) << x).operator<<(endl);

Either way, our two rules apply. The first rule says that the implementation is allowed to evaluate our complicated expression and endl in either order. The second rule says that the complicated expression must be completely evaluated before executing operator<<. That implies that the string x = and the value of x must be written before writing endl, which is what we want.

A similar analysis of the complicated expression shows that x = must be written before the value of x.

Now let’s look at the expression that doesn’t work:

   cout << *p++ << “, “ << *p << endl;

As before, the uses of << group to the left, so that this statement means

   ((((cout << *p++) << “, “) << *p) << endl);

Consider the subexpression

   (((cout << *p++) << “, “) << *p)

Similarly to before, we can view this as a call to operator<< whose right operand is *p and whose left operand is complicated, so that it means either

   operator<<(((cout << *p++) << “, “), *p);

or

   ((cout << *p++) << “, “).operator<<(*p);

depending on whether or not the operator<< being used is a member of the class of cout. As before, both operands must be evaluated before calling operator<<. However, in this case, one operand uses the value of p and the other operand (uses and) modifies it. Because the operands are permitted to be evaluated in either order, it is up to the implementation whether *p uses the value of p before or after it has been modified. In other words, even though the << operation in the subexpression

   cout << *p++

must be evaluated before the << operation that prints *p, that says nothing about the relative order in which *p++ and *p are evaluated. The implementation is permitted to evaluate them in either order and have their results until it is time to print them.

The lesson from the foregoing discussion may be familiar: Expressions with side effects can be dangerous. Although they can be useful, they always require thought. In particular, even though parts of an expression might be guaranteed to be evaluated in particular order, that guarantee is not universal.

8.4.3. Dangling Pointers

It’s virtually impossible to learn C++ without learning about the hazards of dangling pointers. For example, in

   int* p = new int;
   delete p;
   *p = 42;              // <*>?!%#@

the assignment to *p is an error because p no longer points anywhere meaningful. Indeed, the implementation is permitted to change the value of p itself to make it easier to detect the error.

8.4.3.1. Pointers to Dynamic Memory

Pointers often point to dynamically allocated memory, and it is hard to say how to avoid dangling pointers in such circumstances except by a very general rule: Don’t delete an object while there’s still a pointer to it somewhere. Most of the time, figuring out whether a use of delete meets this rule is either very easy or very difficult; in either case, the application of this rule is beyond the scope of this chapter.

However, one thing is well worth noting: Many uses of dynamic memory are intended as containers, which are flexible data structures that grow as needed to contain the objects placed in them. The way to make such things safe is to wrap them in appropriate container classes, think hard enough about those classes to ensure they’re safe, and then use them.

Thus, for instance, instead of saying

   int* p = new int[n];
   // ...
   delete[] p;

think about whether what you really want might be a flexible array. If it is, use an appropriate class from the standard library:

   vector<int> p(n);


Previous Table of Contents Next