Boost logo

Boost :

From: Emil Dotchevski (emildotchevski_at_[hidden])
Date: 2006-06-21 15:33:43


> I was reading this and thinking, is this necessary? But it is. Quite an
> interesting approach that I've never thought of.
>
> How does the catch handler actually process this information?
>
> Could you write an example that would show how you could give detailed
> information? I think I can see it, but I'm not quite sure.

The main issue for me was to decouple the context information from the type
of the exception, and the main reason for this is that the same exception
could be thrown in different contexts (by the same throw statement), and
require different information attached to it.

So the question then was how to store the attachments.

As I mentioned in my previous post, in my implementation I wrote a class
called info, which stores the attachments in a map of boost::any objects,
associated by their type_info. Then I wrote a simple "info wrapper" class
template, something along the following lines:

template <class T,class Tag>
class info_wrapper //value type
{
public:
    info_wrapper( T const & v ): value_(v) { }
    T const & get() const { return value_; }
private:
    T value_;
};

template <class Tag,class T>
info_wrapper<T,Tag> wrap_info( T const & v )
{
    return info_wrapper<T,Tag>(v);
}

template <class Tag,class T>
T const * get_info( info const & xi )
{
    if( info_wrapper<T,Tag> const * w=xi->get< info_wrapper<T,Tag> >() )
        return &w->get();
    else
        return 0;
}

The xi->get<> member function template returns a pointer to the attachment
of the specified type stored in the info object, or null if the info object
doesn't store an attachment of the specified type.

Then I have two other function templates:

template <class Tag>
info_wrapper<std::string,Tag> wrap_string( char const * s )
{
    return wrap_info<Tag>( std::string(s) );
}

template <class Tag>
char const * get_string( info const & xi )
{
    if( std::string const * s = get_info<Tag,std::string>(xi) )
        return s.c_str();
    else
        return 0;
}

Now, with this setup I can attach strings to exceptions, and access them by
their tags, like so:

...
class tag_file_name; //incomplete
info & xi = ....;
xi.add( wrap_string<tag_file_name>("filename.txt") );

At the catch site, I can extract the file name like this:

...
catch( read_error & x )
{
    if( info * xi = dynamic_cast<info *>(&x) )
        if( char const * file_name = get_string<tag_file_name>(*xi) )
        {
        //file_name is the file name attached to the read_error exception.
        }
}

Here is an example which demonstrates the necessity of this system. Consider
the situation when you copy one file to another, by repeatedly calling a
"read" function and a "write" function, each of which could throw. Clearly,
in the context of this copy operation, an exception should have both the
source and the target file name (in a more typical context you only need one
file name attached.) This is how you can accomplish this with the presented
system:

class tag_src_file_name;
class tag_dst_file_name;

...
boost::shared_ptr<FILE> src = io::fopen(src_name,"rb");
boost::shared_ptr<FILE> dst = io::fopen(dst_name,"wb");
try
{
    while( !done )
    {
        read(src,buffer); //throws by throw failed<read_error>()
        write(dst,buffer); //throws by throw failed<write_error>()
    }
}
catch( info & xi )
{
    xi.add( wrap_string<tag_src_file_name>(src_name) );
    xi.add( wrap_string<tag_dst_file_name>(dst_name) );
    throw;
}

Assuming both read_error and write_error derive from io_error, here's what
the catch could look like:

catch( io_error & x )
{
    if( info * xi = dynamic_cast<info *>(&x) )
    {
        if( char const * file_name = get_string<tag_file_name>(*xi) )
            ....; //we've got a file name
        if( char const * src_name = get_string<tag_src_file_name>(*xi) )
            ....; //we've got a source file name
        if( char const * dst_name = get_string<tag_dst_file_name>(*xi) )
            ....; //we've got a destination file name
    }
}

Of course, read_error and write_error exceptions could be processed
separately, if necessary.

And finally, I indicated in my previous post that attachments can be added
directly in the throw-statement. Please don't flame me for the operator<<
overload, it is necessary to allow composition of attachments.

The simplest form of throwing would be this:

throw failed<read_error>();

The above throw-expression throws an exception with no attachments (but with
the ability to have stuff attached to it later.)

You can also do this:

throw failed<read_error>() << wrap_errno();

Here, wrap_errno is a function that captures the current errno value, and
returns it as an object of some type, which is then stored in the info
sub-object of the exception being thrown. Of course, you can attach more
stuff directly in the throw-expression, by following with more calls to
operator<<.

Here is another example from my own code:

namespace file
{
    size_t fread( void * buffer, size_t size, size_t count,
boost::shared_ptr<FILE> const & f )
    {
    assert( f );
    size_t nr = ::fread(buffer,size,count,f.get());
    if( !ferror(f.get()) )
        return nr;
    else
        throw failed<fread_error>()
            << wrap_errno()
            << wrap_function("fread")
            << boost::weak_ptr<FILE>(f);
    }
}

HTH,
Emil


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk