Boost logo

Geometry :

Subject: [ggl] Definition of exception types
From: Hartmut Kaiser (hartmut.kaiser)
Date: 2009-06-14 13:35:38


Mateusz Loskot wrote:

> > A bit off-topic perhaps, but what I prefer when using an API is to
> have the
> > choice between throwing and non-throwing behavior of a particular
> function.
>
> I agree it's good to have the convenience of choice here.
>
> > I think the filesystem library uses a nice way to implement both
> behaviors
> > using the same function:
> >
> > error const throw_error = error(); // global sentinel
> >
> > void some_function(error& e = throw_error)
> > {
> > // error handling:
> > if (&e == &throw_error) {
> > // throw error
> > }
> > else {
> > // set e to the error and return
> > }
> > }
> >
> > Allowing to use the function either as:
> >
> > some_function(); // throws on error
> >
> > or
> >
> > error e;
> > some_function(e); // doesn't throw, but sets 'e' on error
>
> Generally, I like this idea. However, I'm having problems trying to
> imagine how to implement this kind of mechanism in GGL without
> making the API too messed.

Agreed, applying this scheme means to add an optional parameter to all
functions potentially throwing. But the alternative is to have two sets of
functions, which might not be preferable either.

> > I don't think it's a good idea to use the exception types as the
> > error type above.
>
> Do you mean the error type in filesystem example or type per error
> category I listed in my first post?

More in the generic sense. 'error' as the type used in my example above.

> > In fact the file system library uses the system::error type for this
> > purpose. I'm not sure, how to implement that in GGL.
>
> I'm not sure either, but the idea is interesting.

One possibility is to extend the system error mechanism to ggl, very similar
to what asio is doing. This is fairly easy as it involves just two or three
type definitions and a couple of functions. This ggl::error_type type could
be used as the function parameter type above and additionally could be
embedded into the ggl::exceptions, making error handling consistent.
Something along the following lines (that's from the top of my head,
though):

namespace ggl
{
    enum error
    {
        success = 0,
        no_success = 1,
        // more error codes
    };

    char const* const error_names[] =
    {
        "success",
        "no_success",
        // add corresponding error names
    };

    namespace detail
    {
        struct ggl_category : public boost::system::error_category
        {
            const char* name() const { return "GGL"; }
            std::string message(int value) const
            {
                switch (value) {
                case success:
                case no_success:
                // ... in the simplest case something like
                    return std::string("GGL(") + error_names[value] + ")";
                default:
                    break;
                }
                return "GGL(Unknown error)";
            }
        };
    }

    inline boost::system::error_category const& get_ggl_category()
    {
        static detail::ggl_category instance;
        return instance;
    }

 
///////////////////////////////////////////////////////////////////////////
    class error_code : public boost::system::error_code
    {
    public:
        explicit error_code()
          : boost::system::error_code(success, get_ggl_category()) {}

        explicit error_code(error e, char const* msg = "")
          : boost::system::error_code(e, get_ggl_category())
          , message_(msg) {}

        std::string const& get_message() const { return message_; }

    private:
        std::string message_;
    };

    inline error_code
    make_error_code(error e, char const* msg = "")
    {
        return error_code(e, msg);
    }

 
///////////////////////////////////////////////////////////////////////////
    class exception : public boost::system::system_error
    {
    public:
        explicit exception(boost::system::system_error e)
          : boost::system::system_error(e) {}

        exception(error e, char const* msg)
          : boost::system::system_error(make_error_code(e), msg) {}

        ~exception (void) throw() {}

        error get_error() const throw()
        {
            return static_cast<error>(
                this->boost::system::system_error::code().value());
        }
    };

    extern error_code throws; // sentinel

} // namespace ggl

////////////////////////////////////////////////////////////////////////////
///
namespace boost { namespace system
{
    // make sure our errors get recognized by the Boost.System library
    template<> struct is_error_code_enum<ggl::error>
    {
        static const bool value = true;
    };
    template<> struct is_error_condition_enum<ggl::error>
    {
        static const bool value = true;
    };
}}

This allows to use ggl::error_code as the type carrying the actual error
description (making it usable as the parameter for the functions), while
ggl::exception gets thrown, but holds an ggl::error_code internally.
Additionally, as boost::system has been adopted by the C++0x standard, this
integrates well with future library behavior.

> > But I thought to throw it on the table here, at least for the sake of
> > discussion.
>
> IMHO, it's very relevant here.
> Consistent convention on errors handling is one of the hardest to
> define
> but one of most appreciated feature of a library.

Agreed.
Regards Hartmut


Geometry list run by mateusz at loskot.net