C++ – Designing exception classes

cerror handlinglibraries

I'm coding a small library and I'm having some trouble with designing the exception handling. I must say that I am (still) confused by this feature of the C++ language and I tried to read as much as possible on the subject to come to an understanding of what would I have to do to properly work with exception classes.

I decided to use a system_error type of approach taking inspiration from the STL implementation of the future_error class.

I have an enumeration containing the error codes:

enum class my_errc : int
{
    error_x = 100,
    error_z = 101,
    error_y = 102
};

and a single exception class (backed up by an error_category type of structures and everything else needed by the system_error model):

// error category implementation
class my_error_category_impl : public std::error_category
{
    const char* name () const noexcept override
    {
        return "my_lib";
    }

    std::string  message (int ec) const override
    {
        std::string msg;
        switch (my_errc(ec))
        {
        case my_errc::error_x:
            msg = "Failed 1.";
            break;
        case my_errc::error_z:
            msg = "Failed 2.";
            break;
        case my_errc::error_y:
            msg = "Failed 3.";
            break;
        default:
            msg = "unknown.";
        }

        return msg;
    }

    std::error_condition default_error_condition (int ec) const noexcept override
    {
        return std::error_condition(ec, *this);
    }
};

// unique instance of the error category
struct my_category
{
    static const std::error_category& instance () noexcept
    {
        static my_error_category_impl category;
        return category;
    }
};

// overload for error code creation
inline std::error_code make_error_code (my_errc ec) noexcept
{
    return std::error_code(static_cast<int>(ec), my_category::instance());
}

// overload for error condition creation
inline std::error_condition make_error_condition (my_errc ec) noexcept
{
    return std::error_condition(static_cast<int>(ec), my_category::instance());
}

/**
 * Exception type thrown by the lib.
 */
class my_error : public virtual std::runtime_error
{
public:
    explicit my_error (my_errc ec) noexcept :
        std::runtime_error("my_namespace ")
        , internal_code(make_error_code(ec))
    { }

    const char* what () const noexcept override
    {
        return internal_code.message().c_str();
    }

    std::error_code code () const noexcept
    {
        return internal_code;
    }

private:
    std::error_code internal_code;
};

// specialization for error code enumerations
// must be done in the std namespace

    namespace std
    {
    template <>
    struct is_error_code_enum<my_errc> : public true_type { };
    }

I only have a small number of situations in which I throw exceptions illustrated by the error code enumeration.

The above did not sit well with one of my reviewers. He was of the opinion that I should have created a hierarchy of exception classes with a base class derived from std::runtime_error because having the error code embedded in the condition mixes things – exceptions and error codes – and it would be more tedious to deal with a the point of handling; the exception hierarchy would also allow for easy customization of the error message.

One of my arguments was that I wanted to keep it simple, that my library did not need to throw multiple types of exceptions and that the customization is also easy in this case as it is handled automatically – the error_code has an error_category associated with it that translates the code to the proper error message.

I have to say that I did not defend well my choice, testament to the fact that I still have some misunderstandings regarding C++ exceptions.

I would like to know if my design makes sense. What would be the advantages of the other method over the one I chose as I have to admit I fail to see that also? What could I do to improve?

Best Answer

I think your colleague was right: you are designing your exception cases based on how simple it is to implement within the hierarchy, not based on the exception-handling needs of the client code.

With one exception type and an enumeration for the error condition (your solution), if the client code needs to handle single error cases (for example, my_errc::error_x) they must write code like this:

try {
    your_library.exception_thowing_function();
} catch(const my_error& err) {
    switch(err.code()) { // this could also be an if
    case my_errc::error_x:
        // handle error here
        break;
    default:
        throw; // we are not interested in other errors
    }
}

With multiple exception types (having a common base for the entire hierarchy), you can write:

try {
    your_library.exception_thowing_function();
} catch(const my_error_x& err) {
    // handle error here
}

where exception classes look like this:

// base class for all exceptions in your library
class my_error: public std::runtime_error { ... };

// error x: corresponding to my_errc::error_x condition in your code
class my_error_x: public my_error { ... };

When writing a library, the focus should be on it's ease of use, not (necessarily) ease of the internal implementation.

You should only compromize the ease of use (how client code will look like) when the effort of doing it right in the library, is prohibitive.