Exception handling

E

E. Robert Tisdale

garyolsen said:
In C++ what kind of unexpected conditions should be handled as exceptions?

None!

An exception is an *expected* but unpredictable [random] event
that cannot be prevented but but must be "handled" at run time.
Programming errors (bugs) are *not* exceptions.
Bugs cause *unexpected* but predicable events once detected
and can only be "handled" by the programmer by fixing the bug.
Fixing the bug prevents the event from ever occurring again.
You can use the 'assert' C preprocessor macro to help detect bugs.

Whenever possible, an exception should be "handled"
at the point where it is detected.
If it cannot be completely handled at the point where it is detected,
the function must pass enough information back to the calling function
to completely handle the exception and recover. This information
is passed back to the calling function in an "exception" object.
When should not use exception, instead, use regular function returns?

A function may "throw" or "return" an exception [object].
A function that "returns" an exception cannot be used
in any expression that does not test the exception.
For example,

double mySqrt(double x, int* pError) {
double y = sqrt(x);
*pError = errno;
return y;
}

int main(int argc, char* argv[]) {
int Error = 0;
const
double x = atof(argv[1]);
const
double y = 3.0 + (mySqrt(x, &Error) + 30.0)/13.0;
return 0;
}

It isn't possible, in this case, to detect an error in mySqrt
before y is initialized with an invalid result.
Besides dividing by 0, bad memory allocation,
what're the most popular exceptions?

Any function that accepts input from the user
that can't easily be checked before passing it to the function
is a good candidate for exception handling.
In the above example, it is better to test for x < 0.0
before attempting to initialize y.

This topic has been discussed extensively
in the comp.lang.c++ newsgroup so it might be a good idea
to consult Google Groups

http://groups.google.com/
 
J

jeffc

lilburne said:
What code? Where? Why should there be code slowing down
customers application simply to detect bugs at ought to have
been caught during basic devvie testing?

Are you in the same thread I am? Adam wrote, "That he has a core dump
and an error message from the OS, which will allow you to debug it -- or
that he got a message stating, "This program cannot continue due to an
uninitialized variable. Please call customer support.""
 
J

jeffc

Adam Fineman said:
It is significantly more effort to code and maintain that kind of
message vs. a simple assert().

Sure it is. But the basic problem here is that the assert is useless - it's
not compiled into production code.
 
J

jeffc

lilburne said:
What are you doing "in the field"? Isn't it wet, windy, and
cold?

I don't think that's the relevant question - the relevant question is "what
are you doing in your ivory tower? Isn't it quiet, lonely, and lacking in
revenue?"
A message like:

"This program cannot continue due to a null pointer on
line 51 of module xyz."

is practically useless. The question you need to ask is how
did it get to line51 of module xyz with a null pointer? What
was happening at the time?

Hardly useless. Anyway, compare that to an assert.
 
L

lilburne

jeffc said:
Are you in the same thread I am?

Dunno! Adam and myself seem to understand the point
perfectly well.
Adam wrote, "That he has a core dump
and an error message from the OS, which will allow you to debug it -- or
that he got a message stating, "This program cannot continue due to an
uninitialized variable. Please call customer support.""

Actually he said he'd rather have a customer reported a core
dump due to an uninitialized variable than an error message,
because given the core dump he can more quickly find the
cause of the error. Though to be fair an uninitialized
variable is more likely to produce erroneous results rather
than a core dump.

Nevertheless to be able to display an error message rather
than crashing, the program has to be checking for the
condition, and it has to be doing this in the customer
version of the program thus slowing it down. Here is an
example of a bug concerning null pointers.

Version A using guards:

bool error = false;
if (somePtr != 0) {
error = somePtr->someMethod();
// other statements
} else {
error = false;
}


Version B using asserts:

assert(somePtr != 0);
bool error somePtr->someMethod();
// other statements


version B is equivalent to A in debug builds, but faster in
release builds because the check is omitted. If somePtr is
ever 0 in a release build the program crashes, but it never
should be 0. Therefore it doesn't need to be checked in
release because the assert has caught any instances of it
being 0 during testing.
 
L

lilburne

jeffc said:
I don't think that's the relevant question - the relevant question is "what
are you doing in your ivory tower? Isn't it quiet, lonely, and lacking in
revenue?"

Revenue is fine thank you. The latest release has a "New
functionality" document that is over 200pp (note that is new
features not bug fixes) which isn't too bad for a 'mature'
product.

Hardly useless. Anyway, compare that to an assert.

The assert doesn't do anything in a release build, and that
is its advantage, you can afford to put more tests into your
code then you ever would if the tests were to be exercised
in release versions.

Would we ever put a check for validity into every method of
our Point class if it were to be exercised in customer
builds? Of course not, the test will be exercised millions
of times, but in debug versions we'll pay the price because
it eliminates a whole category of bugs due to an
uninitialized Point.

And so it goes on.

Array bounds checking, I don't want no stinking bounds
checking in release, but debug builds yes please. Write code
that overruns the elements in the dynamic array class and
BANG the asserts fire.

Now assertions only work as part of a quality system. You
need to be testing your code, and testing it regularly.
 
D

David Fisher

This comment from jeffc sparked a thought:
The assert doesn't do anything in a release build, and that
is its advantage, you can afford to put more tests into your
code then you ever would if the tests were to be exercised
in release versions.

Would we ever put a check for validity into every method of
our Point class if it were to be exercised in customer
builds? Of course not, the test will be exercised millions
of times
...

Error checking (and recovery) in itself isn't a bad thing in
a release build - it's the cost that people object to. A really
clever compiler could potentially take care of this problem -
for example:

T& Array::eek:perator [] (int index)
{
assert(index >= 0 && index < arrayLength);
return arrayVal[index];
}

void incrementValues(Array &a)
{
for (int n = 0;n < a.length();++n)
{
++a[n];
}
}

An intelligent compiler could recognise that in this particular
loop, the assert condition is always false, and so the check is
not necessary at run time.

Has anyone ever seen an optimising compiler that actually does
this ?

This would require:

- data flow analysis across function boundaries
- some way of preventing execution of the "assert" code:
- multiple entry points to functions with preconditions ?
- multiple versions of functions, with and without checks ?
(similar to the code generation done by templates)
(but avoid having 2^N versions of a function with N asserts !)
- possibly embedding this information in object modules / libraries
so separate compilation is handled, too

This applies to "optimising out" any block of code the compiler
knows will never be entered, not just asserts like this one.

David F
 
L

lilburne

David said:
This comment from jeffc sparked a thought:

The assert doesn't do anything in a release build, and that
is its advantage, you can afford to put more tests into your
code then you ever would if the tests were to be exercised
in release versions.

Would we ever put a check for validity into every method of
our Point class if it were to be exercised in customer
builds? Of course not, the test will be exercised millions
of times
...


Error checking (and recovery) in itself isn't a bad thing in
a release build - it's the cost that people object to. A really
clever compiler could potentially take care of this problem -
for example:

T& Array::eek:perator [] (int index)
{
assert(index >= 0 && index < arrayLength);
return arrayVal[index];
}

void incrementValues(Array &a)
{
for (int n = 0;n < a.length();++n)
{
++a[n];
}
}

An intelligent compiler could recognise that in this particular
loop, the assert condition is always false, and so the check is
not necessary at run time.

Has anyone ever seen an optimising compiler that actually does
this ?

This would require:

- data flow analysis across function boundaries

The code I gave was actually a modification of a test I
wrote for claims by java advocates that runtime JIT
optimization produced better code than a static compiler,
because the optimization could be done dynamically based on
the data at runtime.

Sometimes the compiler can optimize the code away that it
see. In the example the calculation is constant and if you
compile it as:

g++ -O2 point.C -DNDEBUG

it runs in zero time because all the code is optimized away
using GCC 3.3.1. The optimization is done across function
boundaries.

However with the assert some threshold has been reached and
the compiler doesn't notice that Point::is_valid() is
returning a constant too.

That is the problem you can never be sure whether the code
will be optimized or not. The same is true for the inline
hint to the compiler, maybe it will inline the code maybe it
wont, you can't be sure. Nor can you be sure that what was
true in today's version of the compiler will be true in the
next version of the compiler.

GCC 3.2.x produces code for the exception version that is
only 15% slower than the assertion version, with -O3
optimization the exception version runs in the same time. I
guess that bugs forced GCC 3.3.1 to be more conservative in
its optimizations. Personally I think it is unacceptable to
be dependent on compiler version optimization strategy for
your runtime performance.

- some way of preventing execution of the "assert" code:
- multiple entry points to functions with preconditions ?
- multiple versions of functions, with and without checks ?
(similar to the code generation done by templates)
(but avoid having 2^N versions of a function with N asserts !)

You really need to separate out that which are programming
errors and that which can genuinely happen at runtime.
Assert for the former and throw an exception or defensively
code in the later case. Personally I'd only do the later
when dealing with external input, once the data is validated
and under program control you shouldn't have to worry about
bad inputs.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
474,147
Messages
2,570,835
Members
47,383
Latest member
EzraGiffor

Latest Threads

Top