Boost logo

Boost :

From: Emil Dotchevski (emildotchevski_at_[hidden])
Date: 2006-06-20 17:43:22


I've been using exceptions for several years now and while this programming
style is very rewarding in general, one particular problem was bugging me
all along: how do you make sure the catch site has all the information
necessary to process the exception? This problem is trivial only in the case
when the throw site has all the information that the catch site needs; but
in general that is not the case.

A good example that illustrates the problem I'm talking about is a file read
function, which needs to be given the file name, so that if a read error
occurs, the exception object would have the file name stored in it.

The typical solution I've seen, and the one I've been using in the past, is
to somehow pass all relevant information to the function that's throwing the
exception, so that if it throws it can encode it in the exception object.
Clearly, this approach is not ideal; in the file read example, the read
function doesn't need a file name, all it needs to attempt the read and to
detect an error is a file handle. Besides, some files, such as the standard
out, don't have names anyway...

I came up with a solution which seems to solve the problem, and I wanted to
see if there is sufficient interest in it to be added to boost.

Essentially, the idea is to decouple all context information, such as file
names, etc., from the "what went wrong" information. The "what went wrong"
is indicated exclusively by the type of the exception being thrown (e.g.
read_error). Any additional information should be independent of that type,
because in different contexts different information may be relevant, for the
same type of error.

Consider the following throw statement:

throw read_error();

With the system I developed, this same error would be reported like this:

throw failed<read_error>();

Here, 'failed' is a function template, which returns an unnamed temporary of
unspecified type, which derives from the type argument passed to 'failed',
and another class called 'info'. Class info is essentially a map of
boost::any objects, associated by their type_info. So, an object of class
info can store objects of any type, but no more than one instance per type.

In the example above, you can catch the exception as read_error & (or any of
the read_error base types), or as info &.

At the catch site, you simply catch errors (that is, you dispatch on 'what
went wrong'), and then probe the exception object for any context
information available:

catch( read_error & x )
    if( info * xi = dynamic_cast<info *>(&x) )
        if( file_name * fn = xi->get<file_name>() )

How did the file name end up in the info object, if the original exception
was thrown simply by 'throw failed<read_error>()'? Since you can catch the
exception as info &, you can intercept *any* exception thrown by the
'failed' function template -- not to handle it, but to add relevant context
information to it, regardless of what went wrong, like this:

void open_and_read_file( char const * n )
    boost::shared_ptr<FILE> f = io::fopen(n,"rb");
    catch( info & xi )
        xi.add( file_name(n) );

Of course, in the try block, there may be many different function calls;
when we catch info &, we don't care which one of those calls failed: we
catch any exception thrown by the 'failed' function template, add the file
name (since it is relevant to any error that occured in this context), and
then re-throw the original object of unspecified type, as returned by the
'failed' function template. In fact, class info is abstract, which protects
against slicing which would occur if the user tried to throw xi (in the
example above).

I can provide more information about my implementation if needed; it allows
context information to be encoded as demonstrated by the example above, and
also directly in the throw-expression -- for stuff that happens to be known
at the throw site anyway, such as errno, etc.


Boost list run by bdawes at, gregod at, cpdaniel at, john at