
Dean Michael Berris wrote:
On 7/4/06, Emil Dotchevski <emildotchevski@hotmail.com> wrote:
I don't think it can be completely avoided. If you throw failed<foo>, you should catch(foo &) to handle the exception. At this point you do need a dynamic_cast to get to the exception_info sub-object. You could have a virtual function in foo to get you an exception_info *, but I don't think that this is appropriate.
Let's see:
template <typename _T> class failed : public exception_info, public std::exception { public: failed() { }; explicit failed (const _T & e) : _wrapped_exception(e) { };
// ... other common methods ... };
//... try { ... } catch (exception_info & e) { ... }
where `e` is already an exception_info reference.
Yes, but that's how it's done in the exception lib when the exception is caught in otherwise exception-neutral contexts, only for the purpose to add info to it; no need for dynamic_cast there. But when you handle the exception, you shouldn't catch exception_info &, you should catch T (from your example). And now you need dynamic_cast to get to the exception_info sub-object.
This also seems like too much work for exception handling -- which seems harder to do than just creating my own exception class which derives from std::exception, and then a custom constructor that contains a copy of the information object that I know I want to be available from those that will be receiving the exception.
When you say that you can just create your own exception class, do you mean that you'd create a different exception class for each combination of context information you want to bundle in it?
Granted that the idea is that the exceptions themselves will be designed by me, and that I know what code will require what information, it sounds like a reasonable way of going about it -- compared to doing a dynamic_cast all the time, and the interface being not so clear and "developer friendly".
But that's far from granted! Consider this example: void copy_file( char const * name1, char const * name2 ) { boost::shared_ptr<FILE> f1 = my_fopen(name1,"rb"); boost::shared_ptr<FILE> f2 = my_fopen(name2,"wb"); try { my_fread( buf, 1, buf_size, f1 ); //throws by failed<file_read_error>(); my_fwrite( buf, 1, buf_size, f2 ); //throws by failed<file_write_error>(); } catch( exception_info & xi ) { xi << wrap_string<source_name>(name1) << wrap_string<destination_name>(name2); throw; } } OK, you are the designer of the my_fread and my_fwrite functions, and the file_read_error and file_write_error exceptions they throw. How do you know that a user will use them in a copy operation? You can't provide for storing a source and target file name in the exception objects without knowing that, can you? This is why the exception lib is necessary, because the context information is really that: context information. It depends on the context in which the code that throws is being called, and is out of your control and beyond your knowledge. The idea is to always use the failed function template when throwing, even if you don't have info to add to the exception at the point of the throw. This way you enable higher stack loations to add context information by catching you as exception_info &.
Is there a good reason why you're not using a "composite-style" [see GoF composite pattern] exception if you intend to be able to add information as the exception is propagated through the try {} catch(...) {} blocks?
If you catch(...), you can't do anything to the exception object. That's why you need class exception_info: so you can catch(exception_info&) and add info to it, regardless of T in a throw failed<T>(). Or did you mean to use composite pattern to organize the information in an exception_info? Could you illustrate this with an example?
I meant for general catch blocks, I didn't intend to say that the literal catch(...) {} blocks. Something like this:
struct composite_exception { std::vector<composite_exception> _exceptions; void add(const composite_exception & e) { _exceptions.add(composite_exception(e)); }
explicit composite_exception(const composite_exception & other) : _exceptions(other._exceptions) { };
composite_exception() {};
// add common methods here, like "what()" };
So you can do something like:
try { } catch (composite_exception & e) { //... iterate through the vector };
But by catching composite_exception &, you are really using value semantics to handle the exception. It's like sayng that you can always throw integer error codes, then catch( int ), and do a switch-case on the error code. In C++, we use types (not values) to differentiate between the different types of errors. The exception lib does not violate this principle: the value semantics of class exception_info are not intended for figuring out how to respond to the exception. When you throw failed<foo>(), you would catch(foo &) (or any of its public base types) to handle the exception; the exception_info sub-object is there only to provide context information, such as file names or whatever else makes sense for the user to see. --Emil