Boost logo

Boost :

From: John Max Skaller (skaller_at_[hidden])
Date: 2001-08-05 18:04:18


David Abrahams wrote:
 
> I have not looked at the threading case in particular, but I do want to
> weigh in on the side of "exceptions don't have to be errors". Exceptions are
> appropriate where the execution model and (lack of) efficiency matches the
> needs of the programmer.

        In Ocaml, use of exceptions as a standard control structure
is encouraged because the implementation is very efficient
(there's no stack unwinding, for example, because it uses a GC).
Because of this, a richer set of control structures is not needed.

        In C++, the design intent is specifically only for errors,
which is a way of saying that the implementors should try to keep
the cost of code which doesn't throw down at the expense of
loss of performance supporting the case when exceptions are thrown.
But there's more to it than that: see below.

        Pragmatically, there are cases where you use exceptions
in C++ for non-errors because the expected costs are considered
reasonable: non-critical code which is simplified by using EH
to enrich the poor set of control structures, or cancelling
a long running calculation the user no longer cares about --
which is pretty close to an 'error' in my books anyhow.

        The intent is made clear, because not everyone
has such good judgement as Dave, and not everyone is
familiar with performance issues in C++, which may be quite
different to another language (like Java or Ocaml).

        My initial experience with EH in C++ was that
it greatly simplified code. More recently, however,
I've been avoiding exceptions for anything other than
catastrophic errors, and that in a language in which the
implementation is efficient -- because the loss of detail
control just isn't worth it.

        To make this latter point more precise by example:
in a piece of code like:

        try {
                do_something();
                do_domething_else();
        }
        catch ...

you are only handling an error in _either_ of the two
calls. So if you write

        try {
                lookup();
                handle_result();
        }
        catch (not_found_error e)
        {
                handle_lookup_failure();
        }

you might be in for a surprise if 'handle_result'
itself contains a lookup which isn't trapped.

What you _really_ wanted was:

        if (lookup()) handle_result()
        else handle_lookup_failure()

and that's the best way to code it, IMHO.
EH, especially in C++, really is best for handling errors,
failing to find a symbol in a lookup table usually
isn't an error. What's the difference??

The answer has something to do with localisation.
EH is a _global_ phenomena. When you catch an error,
you really can't care much where it happened,
because you can't find that out.

On the other hand, alternate controls, such as
early exits from a search loop, are highly
localised phenomena, and EH in C++ is very bad
at providing it in a secure way (efficiency aside).
You can write:

        try {
                for (int i = 0; i<10; i++)
                {
                        if (a[i]==key) throw Found(i);
                }
                throw NotFound;
        }
        catch (Found result) { cout << "found " << result.i; }
        catch (NotFound) { cout << "not found"; }

and it looks really good, until you realise that BOTH cases
continue on, when you only wanted to continue on if you
found a result. So you have to put ALL that code inside
the catch handler for Found, and you end up with EH nested
in EH nested in EH for a simple sequence of operations
in which the 'not found' result ISN'T an error, but just
requires a different flow of control.

In Ocaml, this is easier, since there is no closing
bracket at the end of the catch,

        try something with
        | Not_found -> error
        | Found i ->
        try next_thing with
        | Not_found -> error
        | Found ->
        try next_thing_after_that with
        ...

but it still begins to look like spagetti. Really, the syntax in C++
is best suited for just writing code without error handling,
and occasionally wrapping a function call in a try/catch block
to report an error at a recovery/translation point.
It really isn't much good for alternate control structures:
it just doesn't seem to compose that well (compared, say,
with expressions, which compose seamlessly).

Dave's 'long calculation terminated by the user' is a good use of EH
precisely because it isn't really an alternate control structure:
the user just wants to kill the calculation. It doesn't matter
exactly where it is up to when it dies. A simple wrapper:

        try { long_winded(); }
        catch (ControlC) { cout << "Cancelled" << endl; }

works fine, because the exception can be trapped way
up a complex call stack, and the semantics are
correct: just kill the calculation. There's no need
to have any try/catch blocks _inside_ the long winded
routine or subroutines it calls, or any complicated
nesting.

Perhaps Dave can comment: I'm really quite interested
in how to use EH well (um, I'd say 'Why EH doesn't
really work' but I'm trying to be more politically acceptable :-)

-- 
John (Max) Skaller, mailto:skaller_at_[hidden] 
10/1 Toxteth Rd Glebe NSW 2037 Australia voice: 61-2-9660-0850
New generation programming language Felix  http://felix.sourceforge.net
Literate Programming tool Interscript     
http://Interscript.sourceforge.net

Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk