Solution

I l @ ve RuBoard

graphics/bulb.gif

Recap: Evaluation Orders and Disorders

1. In each of the following statements, what can you say about the order of evaluation of the functions f , g , and h and the expressions expr1 and expr2 ? Assume that expr1 and expr2 do not contain more function calls.

Ignoring threads, which are not mentioned in the C++ standard, the answer to the first question hinges on the following basic rules:

  1. All of a function's arguments must be fully evaluated before the function is called. This includes the completion of any side effects of expressions used as function arguments.

  2. Once the execution of a function begins, no expressions from the calling function begin or continue to be evaluated until execution of the called function has completed. Function executions never interleave with each other.

  3. Expressions used as function arguments may generally be evaluated in any order, including interleaved, except as otherwise restricted by the other rules.

Given those rules, let's see what happens in our opening examples:

  // Example 20-1(a)   //   f( expr1, expr2 );  

In Example 20-1(a), all we can say is that both expr1 and expr2 must be evaluated before f() is called.

That's it. The compiler may choose to perform the evaluation of expr1 before, after, or interleaved with the evaluation of expr2 . There are enough people who find this surprising that it comes up as a regular question on the newsgroups, but it's just a direct result of the C and C++ rules about sequence points.

  // Example 20-1(b)   //   f( g( expr1 ), h( expr2 ) );  

In Example 20-1(b), the functions and expressions may be evaluated in any order that respects the following rules:

  • expr1 must be evaluated before g() is called.

  • expr2 must be evaluated before h() is called.

  • both g() and h() must complete before f() is called.

  • The evaluations of expr1 and expr2 may be interleaved with each other, but nothing may be interleaved with any of the function calls. For example, no part of the evaluation of expr2 nor the execution of h() may occur from the time g() begins until it ends.

That's it. For example, this means that any one or more of the following are possible:

  • Either g() or h() could be called first.

  • Evaluation of expr1 could begin, then be interrupted by h() being called, then complete. (Likewise for expr2 and g() .)

Some Function Call Exception Safety Problems

2. In your travels through the dusty corners of your company's code archives, you find the following code fragment:

  // Example 20-2   //  // In some header file: void f( T1*, T2* ); // In some implementation file: f( new T1, new T2 ); 

Does this code have any potential exception safety problems? Explain .

Yes, there are several potential exception safety problems.

Brief recap: An expression such as new T1 is called, simply enough, a new-expression. Recall what a new-expression really does (ignoring in-place and array forms for simplicity, because they're not very relevant here):

  • It allocates memory;

  • It constructs a new object in that memory; and

  • If the construction fails because of an exception the allocated memory is freed.

So each new-expression is essentially a series of two function calls: one call to operator new() (either the global one, or one provided by the type of the object being created), and then a call to the constructor.

For Example 20-1, consider what happens if the compiler decides to generate code as follows :

  1. allocate memory for the T1

  2. construct the T1

  3. allocate memory for the T2

  4. construct the T2

  5. call f()

The problem is this: If either step 3 or step 4 fails because of an exception, the C++ standard does not require that the T1 object be destroyed and its memory deallocated. This is a classic memory leak, and clearly Not a Good Thing.

Another possible sequence of events is the following:

  1. allocate memory for the T1

  2. allocate memory for the T2

  3. construct the T1

  4. construct the T2

  5. call f()

This sequence has not one, but two exception safety problems with different effects:

  1. If step 3 fails because of an exception, then the memory allocated for the T1 object is automatically deallocated (step 1 is undone), but the standard does not require that the memory allocated for the T2 object be deallocated. The memory is leaked.

  2. If step 4 fails because of an exception, then the T1 object has been allocated and fully constructed , but the standard does not require that it be destroyed and its memory deallocated. The T1 object is leaked.

"Hmm," you might wonder , "then why does this exception safety loophole exist at all? Why doesn't the standard just prevent the problem by requiring compilers to Do the Right Thing when it comes to cleanup?"

Following the spirit of C in the matter of efficiency, the C++ standard allows the compiler some latitude with the order of evaluation of expressions, because this allows the compiler to perform optimizations that might not otherwise be possible. To permit this, the expression evaluation rules are specified in a way that is not exception-safe, so if you want to write exception-safe code you need to know about, and avoid, these cases. Fortunately, you can do just that and prevent this problem. Perhaps a managed pointer like auto_ptr could help? We'll see the answer in Item 21.

I l @ ve RuBoard


More Exceptional C++
More Exceptional C++: 40 New Engineering Puzzles, Programming Problems, and Solutions
ISBN: 020170434X
EAN: 2147483647
Year: 2001
Pages: 118
Authors: Herb Sutter

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net