Boost logo

Boost :

From: Emil Dotchevski (emildotchevski_at_[hidden])
Date: 2006-08-08 21:42:22


Pavel Vozenilek wrote:
> "Emil Dotchevski" wrote:
>
>
>>>>>> http://article.gmane.org/gmane.comp.lib.boost.devel/146322
>>>>
>>> } catch(boost::exception& e) {
>>> e.dump_everything(); // what happened?
>>> }
>>
>> Yes, but what does dump_everything() look like?
>> [snip]
>
> Would it be possible to use typeid(..).name()
> together with lexical_cast<> on the values?
>
> The formatting is almost irrelevant at this
> point, raw data are.
> The end user woudn't see it, only the author.

You lost me here.

In my mind, formatting a message for the user is the end result of (most)
exception handling code. In general, exception handling code needs to be
able to digest the information stored in exception objects, and present it
to the user. Of course, you would do this after you already know what class
of failure occured (by catching the appropriate type of exception.)

My point in this part of the discussion is that the fact that you could
enumerate all the info in a boost::exception doesn't help you at all in
formatting a meaningful user message, and is therefore just a debug feature.
If you agree with this statement, then I think we should shift the
discussion from "how do we enumerate all info in a boost::exception" to "how
do we generate a complete (not necessarily user-friendly) debug string from
a boost::exception".

>>> typeswitch:
>>> if (dynamic_cast<This>(e)) .... else
>>> if (dynamic_cast<That>(e)) ....
>>
>> What's wrong with
>>
>> catch( This & )
>> {
>> }
>> catch( That & )
>> {
>> }
>>
>
> This looses the valuable simplicity
> of catch (boost::exception&).

You can still catch(boost::exception &) if that's what you want. In the
above example I assumed that you do want to differentiate between a This and
a That.

> And what if you want to add the same
> 10 data items for every exception.

Then you catch(boost::exception&) and add 10 data items to it.

Or, if you want to differentiate between This and That but still add the
same 10 data items to them, you can do:

void add_10_data_items( boost::exception & e );

catch( This & e )
{
    ....
    add_10_data_items(e);
    throw;
}
catch( That & e )
{
    ....
    add_10_data_items(e);
    throw;
}

> [ snip visitor-like approach vs individual catch statements ]
>>
>> My personal opinion is that a list of ordered catch statements is the
>> simplest way to get what you need. And it is directly supported by
>> C++.
>
> Problems:
>
> * Coupling. The code containing catch is likely
> already complex and now it needs to include
> all handled exceptions, even if they are from
> lower layer.

You can handle multiple types of exceptions in a uniform way by catching
their common base type.

> With the other approach you may create
> visitor in lower layer and provide it as part
> of its interface.
> The visitor would not need to know about higher
> layer but would work correctly, the upper layer
> would not depend on lower layer details
> (definitions of the exceptions).

I see your point. You want to automatically translate between a set of (low
level) exception classes, and another set of (high level) exception classes,
while not really worrying about what info is contained in each exception. If
this is the case, you don't need to visit all exception info stored in a
boost::exception, you just want to be able to copy it without understanding
it. Correct?

> * Isolation of error handling.
> With sequence of catch you put error handling
> code into vicinity of normal-path code.
> The code is spread across many functions.
>
> With visitor you can put error handling code
> into one central location and can completely
> isolate it from normal-path code.
> Only visitor definition needs to be shared.

Sure, but how do you handle an exception is quite different from how do you
catch it. I think that dispatching between the different classes of failures
should be done by catching different types of exceptions. Once an exception
is caught, you can still have shared handling for multple exception types:
just separate the shared code in a function, and call it.

> * Not dynamic. Behaviour of a hardcoded catch sequence
> cannot be changed easily. A visitor fares better.
> Not really earth shaking feature but may come handy.
>
> Example: the visitor itself may be payload
> of the thrown exception. On each catch level
> it will be invoked and when it decided
> the error has been solved it will stop propagating
> the exception up.
> Say std::bad_alloc that "auto-stops"
> when enough of memory has been released.
>
> Better example: regression test suite.
> You created and use debug visitor that stops
> propagating of exceptions up and you may safely
> throw and test anything from lower layer w/o worry
> what disaster will it cause somewhere up.

I understand your point, but I don't agree with it. I think catching
exceptions should be hard-coded. Of course, there are many exception-neutral
contexts, but you don't *handle* the exception there. Each exception passes
through all exception-neutral levels and reaches a level which needs to deal
with the problem; at that point, you must know what could possibly fail (as
opposed to trying to handle everything "automatically"), and (typically)
format a message for the user. This message can't be simply "Something
failed, see details below" followed by all info stored in the
boost::exception.

> * It is shorter:
>
> } catch (boost::exception& e) {
> my_visitor v;
> v.process(e);
> }
>
> vs.
>
> } catch (exception1& e) {
> handle(e);
> } catch (exception2& e) {
> handle(e);
> } catch (exception3& e) {
> handle(e);
> } ....

I could argue that v.process(e) only obfuscates the handling logic. But I'd
agree it's shorter. :)

> * Safety: the correct ordering of the
> checking is (should be) automated
> with a visitor. Finding bug in
> hand-written catch sequence
> is very hard.

Finding bugs in exception handling code is very hard anyway. And a visitor
could still be order-dependent, except that this dependency isn't as clear
as the built-in order-dependency of C++ catch statements.

> * If you add new exception or change
> exception hierarchy you need to
> update only the visitor class, not every
> catch.

I find myself repeating what I said earlier, but I think this is the essence
of what you're trying to achieve with the "visitor" approach of hanling
exceptions: you are replacing the "catch-by-type" semantics of C++ with
"catch-everything-and-examine-its-value" semantics. I don't think this is a
good idea; but even if it is a good idea, the proper way of doing that would
be to stop worrying about types, and just throw different values. You can
always throw boost::exception() objects, and stuff an "error code" in them.
Better yet, throw a string, which is an XML-encoded description of the
problem. The ultimate "data-driven" approach!

--Emil


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