Solution

I l @ ve RuBoard

graphics/bulb.gif

Recapping uncaught_exception()

1. What does std::uncaught_exception() do?

The standard uncaught::exception() function provides a way of knowing whether there is an exception currently active. An important thing to note is that this is not the same thing as knowing whether it is safe to throw an exception.

To quote directly from the standard (15.5.3/1):

The function bool uncaught_exception() returns true after completing evaluation of the object to be thrown until completing the initialization of the exception-declaration in the matching handler . This includes stack unwinding . If the exception is rethrown, uncaught_exception() returns true from the point of rethrow until the rethrown exception is caught again .

As it turns out, this specification is deceptively close to being useful.

Background: The Problem with Throwing Destructors

If a destructor throws an exception, Bad Things can happen. Specifically, consider code like the following:

 // Example 19-1: The problem // class X { public:   ~X() { throw 1; } }; void f() {   X x;   throw 2; } // calls X::~X (which throws), then calls terminate() 

If a destructor throws an exception while another exception is already active (that is, during stack unwinding), the program is terminated . This is usually not a good thing.

For more information, see also Exceptional C++ [Sutter00] Item 16, which discusses "Destructors That Throw and Why They're Evil."

The Wrong Solution

"Aha," many people, including many experts, have said, "let's use uncaught_ exception() to figure out whether we can throw!" And that's where the code in Question 2 comes from. It's an attempt to solve the illustrated problem:

2. Consider the following code:

 // Example 19-2: The wrong solution //  T::~T()   {   if( !std::uncaught_exception() )   {   // ... code that could throw ...   }   else   {   // ... code that won't throw ...   }   }  

Is this a good technique? Present arguments for and against .

In short: No, this is not a good technique, even though it attempts to solve a problem. There are technical grounds why this technique shouldn't be used. But I'm more interested in arguing against this idiom on moral grounds.

The idea behind the idiom in Example 19-2 is simple: We'll use the path that could throw as long as it's safe to throw. This philosophy is wrong on two counts. First, this code doesn't do that. Second (and more important, in my opinion), the philosophy itself is in error. Let's investigate these two points separately.

Why the Wrong Solution Is Unsound

The first problem is that the idiom in Example 19-2 won't actually work as intended in some situations. That's because it can end up using the path that doesn't throw even when it would be safe to throw.

 // Example 19-2(a): Why the wrong solution is wrong // U::~U() {   try   {     T t;     // do work   }   catch( ... )   {     // clean up   } } 

If a U object is destroyed because of stack unwinding during exception propagation, T::~T() will fail to use the "code that could throw" path even though it safely could. T::~T() doesn't know that in this case it's already protected by a catch( ... ) block external to itself, up in U::~U() .

Note that none of this is materially different from the following:

 // Example 19-3: Variant, another wrong solution // Transaction::~Transaction() {   if( uncaught_exception() )   {    RollBack();   }   else   {    // ...       } } 

Again, note that this doesn't do the right thing if a transaction is attempted in a destructor that might be called during stack unwinding:

 // Example 19-3(a): Why the variant wrong solution // is still wrong // U::~U() {   try {     Transaction t( /*...*/ );     // do work   } catch( ... ) {     // clean up   } } 

So Example 19-2 doesn't work the way it's intended to work. That's fine, but it's not the main issue.

Why the Wrong Solution Is Immoral

The second and more fundamental problem with this solution is not technical, but moral. It is poor design to give T::~T() two different "modes" of operation for its error reporting semantics. The reason is that it is always poor design to allow an operation to report the same error in two different ways. Making the interface not merely modal, but modal in a way that the calling code can't easily control or account for, has two major failings. It complicates the interface and the semantics. And it makes the caller's life harder because the caller must be able to handle both flavors of error reporting ”and this when far too many programmers don't check errors well in the first place.

Sometimes, when I'm driving my car, I find myself behind another driver who's partly in one lane and partly in the next . After several seconds have passed and the driver is still (from my point of view) behaving erratically, I feel the urge to roll down the window and call out, in a loud, but kind, thoughtful, and caring voice: "C'mon, buddy! Pick a lane! Any lane!" As long as the other driver continues to straddle the line, I have to be ready to handle the possibility of his moving into either lane at any time. This is not only annoying, but it slows down my own progress.

Sometimes, when we're writing code, we find ourselves using another programmer's class or function that has a schizophrenic interface, such as indecisively trying to report the same failure in more than one way, instead of just picking one set of semantics and running with those semantics consistently. We should all feel the urge to walk over to the offender's office or cube and help this poor unenlightened person to make a decision that would simplify life for us and other users.

The Right Solution

The right answer to the Example 19-1 problem is much simpler:

 // Example 19-4: The right solution // T::~T() /* throw() */ {   // ... code that won't throw ... } 

Example 19-4 demonstrates how to make a design decision instead of waffling.

Note that the throw() throws-nothing exception specification is only a comment. That's the style I've chosen to follow, in part because it turns out that exception specifications confer a lot less benefit than they're worth. Whether or not you decide to actually write the specification is a matter of taste. The important thing is that this function won't emit an exception. For a discussion about the possibility of exceptions thrown by destructors of members of T , turn to Item 18.

If necessary, T can provide a "pre-destructor" function (for example, T::Close() ), which can throw and which performs all shutdown of the T object and any resources it owns. That way, the calling code can call T::Close() if it wants to detect hard errors, and T::~T() can be implemented in terms of T::Close() plus a try / catch block:

 // Example 19-5: Alternative right solution // T::Close() {   // ... code that could throw ... } T::~T() /* throw() */ {   try   {     Close();   }   catch( ... ) { } } 

This nicely follows the principle of "one function, one responsibility." A problem in the original code was that it had the same function responsible for both destroying the object and final cleanup/reporting.

See also Items 17 and 18 about why this try block is inside the destructor body and should not be a destructor function try block.

Guideline

graphics/guideline.gif

Never allow an exception to be emitted from a destructor. Write destructors as though they had an exception specification throw() (whether the throw-specification actually appears in the code is a matter of personal taste).


Guideline

graphics/guideline.gif

If a destructor calls a function that might throw, always wrap the call in a try / catch block that prevents the exception from escaping.


3. Is there any other good use for uncaught_exception() ? Discuss and draw conclusions.

Unfortunately, I do not know of any good and safe use for uncaught_ exception() . My advice: Don't use it.

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