|
Boost : |
From: Emil Dotchevski (emildotchevski_at_[hidden])
Date: 2006-07-04 15:09:52
Dean Michael Berris wrote:
> On 7/4/06, Emil Dotchevski <emildotchevski_at_[hidden]> 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
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk