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?