Re: [boost] Proposed library Boost Exception updated

Pavel,
"Emil Dotchevski" wrote:
http://www.revergestudios.com/boost-exception/boost-exception.html
It is also possible to extend the interface to support enumeration of the exception info stored in boost::exception objects, but my personal preference is to first collect more feedback from people using the library, because I'm not sure if this enumeration is needed in practice. Brad King illustrated this idea in his earlier post:
I believe this functionality should be present and will be used extensively at the top level of an application.
Could you provide an example of using this functionality at the top level of an application?
There should be also ability to support visitation of exception objects. Imagine situation:
try { .... } catch (boost::exception& e) { e << ....;
// now some exceptions could be handled // and some need to be passed up ????
The ??? could be typeswitch or it could be a visitor object passed into the exception. The visitor would be able to throw original or new exception or handle the situation. The visitor could be separated, reused and is more dynamic than a typeswitch.
I am not sure I understand what you mean by typeswitch. Can you illustrate your point with more code? --Emil

"Emil Dotchevski" wrote:
Could you provide an example of using this functionality at the top level of an application?
int main() { try { .... run application } catch(boost::exception& e) { e.dump_everything(); // what happened? } } This can be only approximated with chain of catch handlers and manual printing of the data.
There should be also ability to support visitation of exception objects. Imagine situation:
try { .... } catch (boost::exception& e) { e << ....;
// now some exceptions could be handled // and some need to be passed up ????
I am not sure I understand what you mean by typeswitch. Can you illustrate your point with more code?
typeswitch: if (dynamic_cast<This>(e)) .... else if (dynamic_cast<That>(e)) .... A visitor solution: try { ... } catch (boost::exception& e) { e << ... my_visitor m; m.process(e); } Now the visitor will have a typelist of exceptions it does handle and will internally generate a chain of dynamic_casts to select the most appropriate visit() function to be called. My partial inspiration is chapter Visitor from Alexandrescu's Modern C++ Design but it is (IMHO) possible to avoid any modifications of the visited classes (the exceptions) for the cost of more processing on visitor side. It may look like: struct my_visitor : public exception_visitor < mpl::list < std::runtime_error, std::bad_alloc, my_exception
{ void visit(std::runtime_exception& e) { ... } void visit(std::bad:alloc& e) { .... throw; } void visit(my_exception& e) { ....throw new_exception(); } void visit(boost::exception& e) { .. catch all others ... } }; Internally the base class could generate internal functions overloaded for every type and also fill a vector with addresses of these functions. The process() would ten go through the vector to find the best fitting internal functions. The internal function will then call the specific visit(). The trick is to order the checks to place the leat classes first to test. Alexandresu has such a technique and MPL likely as well. It is somehow different way than what GOF do but it provides the same end result (less efficiently but with just a single static type known). /Pavel

Pavel, On 8/8/06, Pavel Vozenilek <pavel_vozenilek@hotmail.com> wrote:
Now the visitor will have a typelist of exceptions it does handle and will internally generate a chain of dynamic_casts to select the most appropriate visit() function to be called.
Have a look at catcher.zip in http://www.boost-consulting.com/vault/index.php?&direction=0&order=&directory=Patterns. That is basically exactly what you are describing. Also, search the developer list for "exception visitor" and you should find a discussion that we are currently having about that code. If you have any questions about the code feel free to e-mail me directly. Jeremy

"Jeremy Day" wrote:
Have a look at catcher.zip in http://www.boost-consulting.com/vault/index.php?&direction=0&order=&directory=Patterns. That is basically exactly what you are describing. Also, search the developer list for "exception visitor" and you should find a discussion that we are currently having about that code.
Interesting. I'd missed that. Btw, BOOST_CATCH symbol is already taken in <boost/detail/no_exceptions_support.hpp>. /Pavel

Pavel Vozenilek wrote:
"Emil Dotchevski" wrote:
Could you provide an example of using this functionality at the top level of an application?
int main() { try { .... run application } catch(boost::exception& e) { e.dump_everything(); // what happened? } }
Yes, but what does dump_everything() look like? The reason why this is important is that I can't imagine dump_everything being able to format a proper user message. The best it can do is dump stuff in a debug log, for example. Which begs the question, how do you format a proper message for the user? I think that the only way to do this is for the code that formats the message to *know*, for each class of exceptions, what info could be available. You can't parameterize this with a "visitor" interface. Or can you? That was my intention when I asked you for an example: what's in dump_everything?
There should be also ability to support visitation of exception objects. Imagine situation:
try { .... } catch (boost::exception& e) { e << ....;
// now some exceptions could be handled // and some need to be passed up ????
I am not sure I understand what you mean by typeswitch. Can you illustrate your point with more code?
typeswitch: if (dynamic_cast<This>(e)) .... else if (dynamic_cast<That>(e)) ....
What's wrong with catch( This & ) { } catch( That & ) { } ...
A visitor solution:
try { ... } catch (boost::exception& e) { e << ...
my_visitor m; m.process(e); }
Now the visitor will have a typelist of exceptions it does handle and will internally generate a chain of dynamic_casts to select the most appropriate visit() function to be called.
You are trying to use value semantics with exception handling. Essentially, I don't see much of a difference between -- class foo: public exception { }; class bar: public exception { }; { .... throw foo(); } ... catch( exception & e ) { if( dynamic_cast<foo *>(&e) ) ....; if( dynamic_cast<bar *>(&e) ) ....; } -- and -- int const foo = 1; int const bar = 2; { .... throw foo; } ... catch( int e ) { if( foo==e ) ....; if( bar==e ) ....; } Even if you use a map of some sort hidden behind a "visitor" interface, 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. I think we should stick to throwing unnamed temporaries, and catching by reference, based on the *type* of the exception object, not its *value*.
My partial inspiration is chapter Visitor from Alexandrescu's Modern C++ Design but it is (IMHO) possible to avoid any modifications of the visited classes (the exceptions) for the cost of more processing on visitor side. [snip]
I understand what you mean, but I still think that this is a departure from the exception handling semantics intended by the C++ designers. This doesn't mean that it doesn't make sense, just pointing that out. 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++. --Emil

"Emil Dotchevski" wrote:
} catch(boost::exception& e) { e.dump_everything(); // what happened? }
Yes, but what does dump_everything() look like?
The reason why this is important is that I can't imagine dump_everything being able to format a proper user message. The best it can do is dump stuff in a debug log, for example. Which begs the question, how do you format a proper message for the user? I think that the only way to do this is for the code that formats the message to *know*, for each class of exceptions, what info could be available. You can't parameterize this with a "visitor" interface. Or can you? That was my intention when I asked you for an example: what's in dump_everything?
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.
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&). And what if you want to add the same 10 data items for every exception. [ 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. 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). * 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. * 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. * 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); } .... * Safety: the correct ordering of the checking is (should be) automated with a visitor. Finding bug in hand-written catch sequence is very hard. * If you add new exception or change exception hierarchy you need to update only the visitor class, not every catch. /Pavel

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

"Emil Dotchevski" wrote:
> http://article.gmane.org/gmane.comp.lib.boost.devel/146322
} catch(boost::exception& e) { e.dump_everything(); // what happened? }
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.
Yes, that's exactly what I think.
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".
Dump via lexical_cast. Perhaps it may be implementable to have member in exception_+info_value that does the dumping, like: struct exc_tag_sqlite_error : boost::exception_info_value<int> { void dump(ostream& os, int value) const { os << "a sqlite error is " << value << "\n"; } }; But even raw hex dump could be valuable. _______________________________________________________ [ visitor-like approach vs individual catch statements ]
* Coupling.
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?
Yes for the (kind a) automatic translation between exceptions. Just copying the old exception may not be sufficient: There are three entities involved: (1) lower layer, (2) higher layer and (3) exception handling in higher layer. The exception handler is a strange beast that may need rather deep knowledge of both lower layer (what really happened) and higher layer (what to do now). I draw an ASCII diagram, should look nice in Notepad or a text editor (A <--- B means B uses A): ------------- ------------------- high level <-- high level error normal path transformer ------------- ------------------- | | | | V V ------------- ------------------- low level <-- low level error normal path generation ------------- ------------------- The normal-path code in higher layer could be decoupled from exception handling which (usually) depends on lower layer. _______________________________________________________
* Isolation of error handling.
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.
Here I praise complete physical separation. The ugly code dealing with errors and their internal changes is out of sight and concentrated on one place. _______________________________________________________
* Safety: the correct ordering
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.
I believe ambiguities could be caught at compile time.
* 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.
Replacing catch-by-type with the other: This is (welcomed) side-effect. My main argument is the ability to decouple normal-path and error-handling code into two independent (or less dependent) parts. _______________________________________________________ Visitor also help with unit testing. You may then manually construct lower layer exceptions and pass them to the visitor and see what happens. Advantages: * it is not necessary to invoke the actual error in lower layer (which may be impossible) * higher layer normal-path is not involved in such testing at all This is not so easy with chain of catches. /Pavel

"Emil Dotchevski" wrote:
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.
Actually, the way exceptions are handled is completely independent of boost::exception itself. One could use chain of catch, Jeremy Day's library or the approach suggested by Andreas Huber or any combination. The explicit support for a "exception transformer" inside boost::exception is not needed (visiting all stored data is a different thing). So my arguing is OT in this regard. /Pavel

Pavel Vozenilek wrote:
The explicit support for a "exception transformer" inside boost::exception is not needed (visiting all stored data is a different thing).
So my arguing is OT in this regard.
Yes, we were discussing how and if enumerating info stored in boost::exception should be supported. I think that instead of enumerating info, we need two other functions: 1. The ability to copy the contained info in one boost::exception to another boost::exception, for folks like you who want to translate exceptions. I could support the following syntax: class my_exception: public boost::exception { }; class translated_exception: public boost::exception { }; catch( my_exception & x ) { throw translated_exception() << x; } 2. The ability to dump all info stored in boost::exception objects, for debugging purposes. Something like: boost::exception & x; std::string info( x.what() ); //get a string of all info stored in boost::exception. Is this alright? Does anyone still think that it is necessary to support enumeration of the info contained in boost::exception? --Emil P.S. The full documentation for Boost Exception is here: http://www.revergestudios.com/boost-exception/boost-exception.htm

"Emil Dotchevski" wrote:
1. The ability to copy the contained info in one boost::exception to another boost::exception, for folks like you who want to translate exceptions. I could support the following syntax:
class my_exception: public boost::exception { }; class translated_exception: public boost::exception { };
catch( my_exception & x ) { throw translated_exception() << x; }
I see problem that common tags (like file/line) from the old exception may overwrite tags in the new one (or vice versa). A semi-standardized tag "previous_exception" may be better here: catch( low_level_exception& e) { throw high_level_exception() ... << exception_info<boost::exception_tag::previous>(e); } This could also make the text dumps more structured.
2. The ability to dump all info stored in boost::exception objects, for debugging purposes. Something like:
boost::exception & x;
std::string info( x.what() ); //get a string of all info stored in boost::exception.
Is this alright? Does anyone still think that it is necessary to support enumeration of the info contained in boost::exception?
IMHO the dump is enough. Without reflection the enumeration is not of much use. ----------- A helper for capturing stack trace may come handy. Say: void foo() { throw boost::exception() << .... } void foo1() { try { foo(); } catch (boost::exception& e) { e.add_stack_trace(BOOST_CURRENT_FUNCTION); throw; } } void foo2() { try { foo1(); } catch (boost::exception& e) { e.add_stack_trace(BOOST_CURRENT_FUNCTION); throw; } } Inside would be list of const char* pointers. The main advantage would be easy to type syntax - no need to check prior existence of the data or anything else, just a single line. The debugging text dump may know about it and pretty print the stack. If the ability to chain exception is standardized then the individual sub-stack traces may get merged when printed. Functions may be even written as void foo() try { ... } catch (boost::exception& e) { e.add_stack_trace(BOOST_CURRENT_FUNCTION); throw; } i.e. no outer brackets. Some compilers do not emit any code for the "try" so it may end up more efficient than classical stack-trace via a macro on the top of function body. /Pavel
participants (3)
-
Emil Dotchevski
-
Jeremy Day
-
Pavel Vozenilek