Exception handling

G

garyolsen

In C++ what kind of unexpected conditions should be handled as exceptions?
Besides dividing by 0, bad memory allocation, what're the most popular
exceptions?

When should not use exception, instead, use regular function returns?

Thanks!
 
M

Mike Wahler

garyolsen said:
In C++ what kind of unexpected conditions should be handled as exceptions?
Besides dividing by 0,

AFAIK, divide by zero does not cause an exception to be thrown.
Of course you could check for it beforehand, and throw your
own exception.
bad memory allocation,

C++'s operator new throws a 'std::bad_alloc' exception if
unable to satisfy an allocation request.
what're the most popular
exceptions?

Popular? C++ defines certain circumstances upon which an
exception is thrown, and which one. I suppose this behavior
will be 'popular' with some folks, and not so with others.
When should not use exception, instead, use regular function returns?

I use the general 'rule of thumb' that if a condition is
truly 'exceptional' (i.e. it's not possible for the program
to continue in a reasonable manner), then I throw (or propagate
an already thrown) an exception, but if it's possible to correct
a 'error' condition (either programmatically or with via user
intervention), I use a return value to indicate an error, allowing
the caller to retry the operation that failed.

-Mike
 
G

Gianni Mariani

garyolsen said:
In C++ what kind of unexpected conditions should be handled as exceptions?
Besides dividing by 0, bad memory allocation, what're the most popular
exceptions?

When should not use exception, instead, use regular function returns?

The answer to this question varies greatly between very experienced C++
programmers.

Paradigm 1.

Use exceptions to manage situations where somthing that is unusual
happens. For example, if you have a "file object" and the file fails to
be created (on disk) then throw an exception to indicate that the object
is not constructed - btw throwing an exception is the only way to
indicate that a constructor failed. In other words, if you were writing
some code e.g.


file d_file( "file.name" ); // throws on open failure

d_file.write( bunch_o_bytes ); // throws when outa space

file d_copy( "file2.name" );

d_copy = d_file; // throws when copy failed.

Notice that all the error handing is managed for you.

However, who deletes file2.name when the copy failed or what happens to
file.name when bunch_o_bytes failed to write all the bytes - does it
roll-back ?


Paradigm 2.

Use exceptions only in EXCEPTIONAL circumstances. This makes some of
the code more mundane, however it forces the programmer to consider the
situation and ends up being cleaner code. (that's the theory). Error
conditions are returned or available as object state. Only "failure"
end up throwing an exception.

The code however is awash with if statements and lots of code to deal
with "failure" return values.


Personally, I subscribe to Paradigm 2 for somewhat historical reasons as
well as experience. Originally, compilers did nasty stuff with
exceptions and debuggers made it difficult to diagnose the actual error.
Also, for example, consider the code in View 1 above, I have seen
sloppy code were either the error is not written anywhere and you have
NO idea why the code just exited OR it comes back and says "failed to
create file" - well - which file ? - what was the error ? So, some of
my issues have been addressed but the potential for programmers to write
sloppy code in Paradigm 1 is really my biggest concern. It's alot
easier to inspect that someone has done the right thing when all the
error handling is explicit. I'm willing to be persuaded though.
 
J

jeffc

garyolsen said:
In C++ what kind of unexpected conditions should be handled as exceptions?
Besides dividing by 0, bad memory allocation, what're the most popular
exceptions?

When should not use exception, instead, use regular function returns?

Basic rule of thumb is: if the function can NOT do what it was asked to do,
and can't find any kind of reasonable workaround, then throw an exception.
Otherwise, use regular function returns.
 
L

lilburne

jeffc said:
Basic rule of thumb is: if the function can NOT do what it was asked to do,
and can't find any kind of reasonable workaround, then throw an exception.
Otherwise, use regular function returns.

Hmmm!

This is a little bit of fun. Say I have a Point class which
I want to have a default constructor so I can do things like:

Point start,end;
curve.end_points(start,end);

Now the problem with a default constructor for something
like Point is that there is no valid default you can give
it. So code like:

Point a,b;
Vector vec(a-b);

is as bad as:

int a,b
int c = a-b;

we'd like to detect when a Point's value is being used
before it is properly set. One way to do this would be to
add some validity flag to the Point class which is false
when default constructed and set true once the point has
been properly initialized. Another way is to encode some
impossible value into the the z value (1.79769e+308). I'll
take the later approach and add a method to check the
validity too:


#include <iostream>
#include <ctime>
#include <cmath>
#include <limits>
#include <cassert>

using namespace std;

class Point{
double m_x;
double m_y;
double m_z;
public:
Point() {
m_z = numeric_limits<double>::max();
}
Point(double x, double y, double z) {set(x,y,z);}
void set(double x, double y, double z) {m_x = x, m_y =
y; m_z = z;}
const double& x() const { return m_x;}
const double& y() const { return m_y;}
const double& z() const { return m_y;}
bool coincident(const Point& a, double eps) const;
bool is_valid() const {
return m_z != numeric_limits<double>::max();
}
};


Now lets consider the function Point::coincident() which
returns true if two points are concident to some tolerance.
However comparing points that haven't been properly
initialized is something we ought to guard against. So I'll
present two versions one using assertions and one using
exceptions, a main to test it:


#ifdef USE_EXCEPTIONS
bool Point::coincident(const Point& a, double eps) const
{
if (!is_valid()) {
throw int(1);
}
return abs(x()-a.x()) < eps &&
abs(y()-a.y()) < eps &&
abs(z()-a.z()) < eps;
}

void test(const Point& a, const Point& b)
{
try {
a.coincident(b, 1e-5);
}
catch (int) {
cout << "caught exception" << endl;
}
}
#else
bool Point::coincident(const Point& a, double eps) const
{
assert(is_valid());
return abs(x()-a.x()) < eps &&
abs(y()-a.y()) < eps &&
abs(z()-a.z()) < eps;
}

void test(const Point& a, const Point& b)
{
a.coincident(b, 1e-5);
}
#endif


int main()
{
time_t start = time(0);
Point a(10.0,10.0,20.0);
Point b(10.0,10.0,20.0);
for (int i = 0; i < 10000000; ++i) {
test(a,b);
}

cout << time(0) - start << endl;
return 0;
}

ideally each of the Point::x(), Point::y() and Point::z()
functions should also check that their instance is valid too
but if you compile and run the program you'll see why I
haven't done that.

Using GCC-3.3.1 on my steam powered PC I get the following
results:

g++ point.C -O2
../a.exe
lapsed time: 6


g++ point.C -O2 -DUSE_EXCEPTIONS
../a.exe
lapsed time: 35

Draw your own moral!
 
A

Adam Fineman

lilburne wrote:
we'd like to detect when a Point's value is being used before it is
properly set.
<snip>

This situation is not an "exceptional" situation at all. This is a bug,
in which case neither exceptions or return codes are appropriate.
assert() is the way to go here.

An attempt to use an uninitialized _anything_ is a bug, and you should
not try to handle bugs gracefully. Your program should crash, and crash
loudly, so you can start fixing the problem. In a real program, perhaps
this indicates a race condition or a problem in an input validation
routine. Whatever the cause may be, you certainly don't want to handle
it gracefully; you want it to crash immediately.


I realize lilburne was pointing out the overhead associated with
exceptions. However, I've seen too many developers deliberating on
using exceptions or return values what a simple assert() is far superior
to either given the situation.

- Adam
 
L

lilburne

Adam said:
lilburne wrote:


<snip>

This situation is not an "exceptional" situation at all. This is a bug,
in which case neither exceptions or return codes are appropriate.
assert() is the way to go here.

An attempt to use an uninitialized _anything_ is a bug, and you should
not try to handle bugs gracefully. Your program should crash, and crash
loudly, so you can start fixing the problem. In a real program, perhaps
this indicates a race condition or a problem in an input validation
routine. Whatever the cause may be, you certainly don't want to handle
it gracefully; you want it to crash immediately.

You'd love our code. Very agressive, minimal defensive
coding, more assertions than 'real' code. Not a single throw
in sight.

I realize lilburne was pointing out the overhead associated with
exceptions. However, I've seen too many developers deliberating on
using exceptions or return values what a simple assert() is far superior
to either given the situation.

Sadly too true. I blame java.
 
J

jeffc

lilburne said:
it. So code like:

Point a,b;
Vector vec(a-b);

is as bad as:

int a,b
int c = a-b;

I can't say I ran your code, but in general I don't consider this relevant.
This is just bad code. If we "took exception" to all potential occurrences
of bad code (presumably you don't *know* it's bad, or you wouldn't have done
it), then our code would be 90% error checking and exceptions, and then we'd
have to get into the error checking and exceptions on our error checking and
exceptions, at which time the amount of actual work done in our program
approaches 0 percent.
 
J

jeffc

Adam Fineman said:
I realize lilburne was pointing out the overhead associated with
exceptions. However, I've seen too many developers deliberating on
using exceptions or return values what a simple assert() is far superior
to either given the situation.

I hardly ever use assert, because it's debug only. I need error checking I
can depend on "in the field" as well as "in house". Why use 2 different
systems?
 
J

jeffc

Adam Fineman said:
I also don't think that it's generally a good idea to have "release" and
"debug" versions of anything, unless it's proven through profiling that
it's necessary.

Look at it this way: When (not if) an end user finds a bug in your
application, what do you want him to report? 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."

Bugs are easier to find if they crash the program. Why would anyone
want to handle such a bug gracefully, if it makes it harder to fix?

Those are not mutually exclusive things, as you assume. It's not much
harder to say "This program cannot continue due to a null pointer on line 51
of module xyz."
 
A

Adam Fineman

jeffc said:
I hardly ever use assert, because it's debug only. I need error checking I
can depend on "in the field" as well as "in house". Why use 2 different
systems?
I also don't think that it's generally a good idea to have "release" and
"debug" versions of anything, unless it's proven through profiling that
it's necessary.

Look at it this way: When (not if) an end user finds a bug in your
application, what do you want him to report? 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."

Bugs are easier to find if they crash the program. Why would anyone
want to handle such a bug gracefully, if it makes it harder to fix?

Of course, I am not referring to life-critical or fault-tolerant
applications.

- Adam
 
L

lilburne

jeffc said:
Those are not mutually exclusive things, as you assume. It's not much
harder to say "This program cannot continue due to a null pointer on line 51
of module xyz."

Well to use your phrase "That is just bad code." You say you
wouldn't test for such so just:

If we "took exception" to all potential occurrences
of bad code (presumably you don't *know* it's bad, or you
wouldn't have done it), then our code would be 90% error
checking and exceptions,

so just how are you going to get "This program cannot
continue due to a null pointer on line 51 of module xyz."
displayed?
 
L

lilburne

jeffc said:
I can't say I ran your code, but in general I don't consider this relevant.
This is just bad code. If we "took exception" to all potential occurrences
of bad code (presumably you don't *know* it's bad, or you wouldn't have done
it), then our code would be 90% error checking and exceptions, and then we'd
have to get into the error checking and exceptions on our error checking and
exceptions, at which time the amount of actual work done in our program
approaches 0 percent.

That is exactly what you want. We have up to 95% overhead in
our debug builds due to assertions of the type described.
Many 'one' or 'two' line functions will have a dozen lines
of assertions.

And yes you test the asserts - that given bad inputs the
methods assert.
 
L

lilburne

jeffc said:
I hardly ever use assert, because it's debug only. I need error checking I
can depend on "in the field" as well as "in house". Why use 2 different
systems?

Each method of our Point class asserts is_valid(), anyone
using a Point that they haven't properly initialized will
find the application program and test program assert the
moment the Point is used. The result is that uninitialized
Points in exercised code simply don't survive. The only way
a developer can get code containing an uninitialized Point
accepted by integration is if it is in some obscure pathway
that isn't exercised by any test. What happens when an
asssertion fires? Well in our development builds the
debugger is fired up and the developer is looking at
location of first use with a full stack trace. No hunting
around trying to find out where.
 
J

jeffc

lilburne said:
That is exactly what you want. We have up to 95% overhead in
our debug builds due to assertions of the type described.

I didn't say anything about assertions.
 
A

Adam Fineman

jeffc said:
Those are not mutually exclusive things, as you assume. It's not much
harder to say "This program cannot continue due to a null pointer on line 51
of module xyz."
It is significantly more effort to code and maintain that kind of
message vs. a simple assert(). Moreover, the failed assert() can also
give you a core dump, which is useful for figuring out *why* there is a
null pointer on line 51.

- Adam
 
J

jeffc

lilburne said:
Well to use your phrase "That is just bad code." You say you
wouldn't test for such so just:

If we "took exception" to all potential occurrences
of bad code (presumably you don't *know* it's bad, or you
wouldn't have done it), then our code would be 90% error
checking and exceptions,

so just how are you going to get "This program cannot
continue due to a null pointer on line 51 of module xyz."
displayed?

You've got code there anyway to say there's an error. Rather than
preferring an abend (which can NOT necessarily be reproduced, regardless of
Adam's premise), just add a macro for line and file to the error statement
that already exists.
 
J

jeffc

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

That's beside the point. The assert never happens in production code, so
you get NOTHING "in the field".
 
L

lilburne

jeffc said:
You've got code there anyway to say there's an error. Rather than
preferring an abend (which can NOT necessarily be reproduced, regardless of
Adam's premise), just add a macro for line and file to the error statement
that already exists.

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?
 
L

lilburne

jeffc said:
line 51



That's beside the point. The assert never happens in production code, so
you get NOTHING "in the field".

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

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?

Customer reports "When I apply X to this model of a turbine
blade the result is incorrect." Devvie loads customers model
into debug application and applies X. Debugger pops up with
assertion and stack trace at the point that things starting
to go wrong. Usually takes less than 30 minutes, from the
start of a devvie taking an interest to the cause being
discovered.
 

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

No members online now.

Forum statistics

Threads
474,147
Messages
2,570,835
Members
47,382
Latest member
MichaleStr

Latest Threads

Top