
Pavel Vozenilek wrote:
"Emil Dotchevski" wrote:
} 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