C/C++ Contributing Editors


Uncaught Exceptions: Fuzzy Math

Bobby Schmidt

Compared to certain flawed political processes, computing processes yield the same answers with remarkable consistency. Now we just need to figure out why they’re wrong.


Copyright © 2001 Robert H. Schmidt

Around the time this column is published, we in America are supposed to swear in a new President. As I type, the presidential election is still not final. Separating myself from the immediate political implications, I’m fascinated to watch this historical drama unfold.

I’m especially amused by so many people ignoring the phenomenon of statistical error. The Florida voting process cannot perfectly capture the combined intent of Florida’s voters. I suspect the vote difference — a few hundred, out of almost six million cast — is well within the process’s margin of error. Trying to extract a “correct” number within that error margin is like cranking up the power of a microscope that’s already at its limit: all you do is magnify the fuzz and uncertainty without measuring anything reproducible or significant.

Put another way: if the same set of Florida voters went out and voted the same way ten times, the vote count tabulated would still differ each time. We simply can’t have a 100% reproducible result with current law and techniques.

In this regard, we in the software development industry live a bit of fantasy. With the right program constraints, we actually can have results that are 100% reproducible. Indeed, for me to answer your questions in this column, I must rely on compilers doing the same thing with the same code on all systems every time.

And as lazy as it sounds, I must confess: I’m quite happy limiting myself to such easily reproducible software behavior. I don’t miss my days as a Windows programmer, with all the uncertainty that multi-threading asynchronous-event model brought. If I were forced to be a real Windows developer again, I think I’d retire from programming altogether, move to West Palm Beach, and design voting ballots.

Existence vs. Accessibility

Q

Which of the following statements are accurate?

A derived class inherits

A derived class does not inherit

(I have seen other books that state that private members are not inherited.)

What is the precise answer? — David N. S. Reeves

A

You’ve really made six different assertions, which I’ll comment on in order.

1) A derived class inherits every data member of its base classes, even when those base members are private.

2) A derived class inherits every ordinary function — and most member functions — of its base classes, even when those base functions are private.

3) A derived class may or may not have the same initial layout as its bases.

4) A derived class does not inherit the base-class constructors and destructor.

5) A derived class does not inherit the base-class assignment operators.

6) A derived class does not inherit friendship relationships from its bases.

As your principle interest seems to lie in points 1 and 2, consider the illustrative example in Listing 1. If you try to build that listing, you should see these results:

Listing 2 contains a much simpler example. When run on my system, the listing produces

sizeof(base)    == 40
sizeof(derived) == 40

Your specific results may vary, depending on compiler-specific vagaries; but on all systems, you’ll find that sizeof(derived) is always at least sizeof(base), because derived contains the private array from base.

Dial 911

Q

Hi,

I have a question regarding the removal of C++ style exception handling from my code. I tried SEH (Structured Exception Handling); but there, if an exception occurs in the constructor, the destructor does not get executed.

Since I cannot return error codes from a constructor, can you suggest a way that I can handle exceptions in a constructor, so that the program restores or exits cleanly (i.e., after calling destructors)? — "kshitij"

A

Some background for the Microsoft-challenged:

Structured Exception Handling, or SEH, is Microsoft’s proprietary exception-handling mechanism for Win32. SEH is a platform feature, not a language feature, and is therefore available (in theory) to all Win32 languages. Microsoft developed SEH before the ascendancy of C++ in the Win32 world. As a result, SEH and C++ exception handling have some fundamental design differences.

Now back to our regularly scheduled programming...

I think you are trying to solve three distinct problems:

On the first problem: you can handle only some constructor exceptions. In particular, you can handle those generated within the constructor body:

class X
    {
    X()
        {
        try
            {
            // ...
            }
        catch (...)
            {
            }
        }
     };

Any exceptions thrown within the try clause are handled by the catch clause, the constructor exits normally, and the resulting object is considered fully constructed.

However, if an exception occurs before the constructor body is entered, you are out of luck:

X() : m() // exception here...
    {
    // ...can’t be handled here
    }

Here m is a data member or base class of X. If m’s constructor throws, you have no chance to handle the exception in X::X’s body.

As a compromise, you can filter the exception with a function try block:

X()
try : m() // exception
    {
    // body of X::X
    }
catch (...)
    {
    // An exception will still be
    // emitted from this catch
    // clause.
    }

According to the language rules, a function try block must throw. It can throw an exception other than the one caught, but it still must throw. Thus, the try block cannot prevent the construction from failing. At best, the block offers a chance to clean up program resources or otherwise recover from the exception.

For your second problem: I think an exception is overall the most effective way for a constructor to dial 911 [1].

Think of an exception as an alternative return-value path. In the example

class X
    {
public:
    X(int &) throw(long);
    // ...
    };

the X constructor has two ways to return values:

I generally recommend using the parameter to "return" success, and the exception to "return" failure. With such a scheme, normal code flow is not interrupted by successful construction. At the same time, the existence of an exception — independent of the value of that exception — implies a construction failure:

try
    {
    int n;
    X x(n);
    // x constructed, and n is set.
    }
catch (long)
    {
    // x failed to construct.
    // You may exit program, or
    // otherwise clean up.
    }

On your third problem, the C++ Standard is pretty clear: destructors are called only for objects that fully construct — that is, for objects whose constructors don’t emit exceptions. If an object’s constructor does emit an exception for any reason, the object does not exist and its destructor is never called.

You may debate the wisdom of this construction/destruction pattern. Lord knows I have [2]. But whether you agree with the design or not, it’s part of the C++ object model, and is enforced by Standard-conformant compilers.

Even if you want to subvert the pattern, you can’t, or at least not easily. In the example above, you can’t even explicitly call the x destructor:

try
    {
    int n;
    X x(n);
    }
catch (long)
    {
    x.~X(); // wrong
    }

since x does not exist in the scope of the catch clause.

You could try an end run such as

X *x = NULL;
try
    {
    int n;
    x = new X(n);
    }
catch (long)
    {
    delete x;
    }

This will compile. It will also induce undefined behavior when run. If the X constructor throws during new X(n), the allocated memory will be automatically deallocated via operator delete. Evaluating delete x on memory that’s already been deallocated would be a bad plan.

I recommend you not attempt to thwart the language design here. If your constructor gets in trouble, throw an exception, and don’t try to subvert the language rules by forcing a destruction.

VC++ Blues

Q

The included code snippet produces a run-time error when compiled with the Visual Studio 6.0 C++ compiler.

The program dies when operator delete is called (that is, after Microsoft’s debug version of free is called). Any of several seemingly unrelated changes fixes the problem.

I am confident that this is legal Standard C++. Originally base was an abstract base class for certain kinds of predicates, and was itself derived from a typedef to std::unary_function.

I would appreciate your comment. Thank you in advance.

P.S. Yes, I know it is illegal to delete a child trough a base pointer with base not having a virtual destructor. This does not seem to be the case here. — Bosko Zivaljevic

A

I’ve distilled your code down to Listing 3.

I am able to replicate your problem with Visual C++ v6. On my machine, the program bombs at the point of delete p with the scary dialog

Debug Error!

Program O:\VS_6\UHOH.EXE

DAMAGE: after Normal block (#27) at 0x007803E0.

(Press Retry to debug the application)

On all the other systems I use, I get no run-time errors of any kind.

Like you, I found that tweaking the program even slightly made the error go away. Among other things, I was able to banish the problem by:

This is certainly a bug, either in the C++ compiler or the run-time library. On a lark, I added my own global operator new and operator delete:

#include <new>
#include <stdlib.h>

void *operator new(size_t n)
    {
    return malloc(n);
    }

void operator delete(void *p)
    {
    free(p);
    }

The problem persisted. However, if I changed the functions so that they did not call malloc and free, the problem went away. Following that lead, I also tried calling malloc and free directly:

derived *p = (derived *)
   malloc(sizeof(*p));
new(p) derived(d);
free(p);

using placement new to construct within *p.

With these changes, the problem remained. This fact strongly suggests a library bug, probably in the heap manager.

Erratica

An anonymous Diligent Reader found a minor bug in my December 2000 column. In Listing 1 I have the line

i = 123;

That line should be removed.

Listing 1 was part of my answer to item "Round and Round Again." In regard to that answer, the reader continued

“I noticed that you ended by suggesting the self-assignment check. My own view has simply been to ridicule the form as being not valid C++ and ignore it; the alternative would be to recommend writing a self-assignment check in every copy constructor (if we recommend it for one class, we have to recommend it for all — it can happen for any class). Do you agree/disagree? Just curious.”

When I wrote my original answer, I wasn’t thinking about it in the way suggested. I was really only picking a simple solution to a specific problem. But the reader got me thinking about the problem more globally, by implying that we should use the technique either everywhere or nowhere.

Guards against self-initialization are closely related to guards against self-assignment, which are C++ 101 orthodoxy [3]. Should we consider self-intialization guards canonical as well? I don’t think so, if only because the opportunities for self-initialization are so limited [4].

Until I got the original question, it had never occurred to me that self-initialization was a problem. I don’t think I’ve ever been bitten by it, or know anyone who has been. I imagine the problem does show up, as do other low-probability events that we don’t routinely worry about or guard against.

In the end, we are really talking about insurance — weighing the risk of no coverage against the cost of coverage. In this case, I think I’m willing to recommend that we go without coverage and risk occasional out-of-pocket payments.

Notes

[1] Lest I be accused of U.S. bias: 911 is the fairly universal U.S. phone number for emergency rescue help. The equivalent number is different in other countries. When the U.S. syndicated show Rescue 911 appeared on Australian television, each episode ended with a disclaimer, telling viewers to dial 000 instead of 911. Apparently some Aussies had dialed 911 by mistake after seeing the show and were unable to get help. Oops.

[2] http://msdn.microsoft.com/library/welcome/dsmsdn/deep02172000.htm

[3] Or were, before Herb Sutter taught us the virtues of exception safety.

[4] Herb Sutter discusses this very point in Item 38 of his book Exceptional C++.

Bobby Schmidt is a freelance writer, teacher, consultant, and programmer. He is also an alumnus of Microsoft, a speaker at the Software Development and Embedded Systems Conferences, and an original "associate" of (Dan) Saks & Associates. In other career incarnations, Bobby has been a pool hall operator, radio DJ, private investigator, and astronomer. You may summon him on the Internet via BobbySchmidt@mac.com.