Я пишу небольшую библиотеку, и у меня возникли проблемы с проектированием обработки исключений. Я должен сказать, что меня (все еще) смущает эта особенность языка C ++, и я постарался прочитать как можно больше по этому вопросу, чтобы понять, что мне нужно сделать, чтобы правильно работать с классами исключений.
Я решил использовать system_error
тип подхода, основанный на реализации future_error
класса STL .
У меня есть перечисление, содержащее коды ошибок:
enum class my_errc : int
{
error_x = 100,
error_z = 101,
error_y = 102
};
и один класс исключений (поддерживается error_category
типом структур и всем остальным, что необходимо system_error
модели):
// 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 { };
}
У меня есть только небольшое количество ситуаций, в которых я выкидываю исключения, проиллюстрированные перечислением кода ошибки.
Вышесказанное не очень понравилось одному из моих рецензентов. Он считал, что я должен был создать иерархию классов исключений с базовым классом, полученным из- std::runtime_error
за того, что код ошибки, встроенный в условие, смешивает вещи - исключения и коды ошибок - и было бы более утомительно разбираться с точкой обработки; иерархия исключений также позволит легко настроить сообщение об ошибке.
Один из моих аргументов состоял в том, что я хотел сохранить простоту, чтобы моей библиотеке не нужно было генерировать несколько типов исключений, и что в этом случае настройка также проста, так как она обрабатывается автоматически - с error_code
ней error_category
связана функция, которая переводит код для правильного сообщения об ошибке.
Я должен сказать, что я не очень хорошо отстаивал свой выбор, свидетельствуя о том, что у меня все еще есть некоторые недоразумения относительно исключений C ++.
Я хотел бы знать, имеет ли мой дизайн смысл. Каковы будут преимущества другого метода перед тем, который я выбрал, поскольку должен признать, что я тоже не вижу этого? Что я могу сделать, чтобы улучшить?
источник
Ответы:
Я думаю, что ваш коллега был прав: вы разрабатываете свои исключения, основываясь на том, насколько просто это реализовать в иерархии, а не на потребностях клиента в обработке исключений.
С одним типом исключения и перечислением для условия ошибки (ваше решение), если клиентский код должен обрабатывать отдельные случаи ошибок (например,
my_errc::error_x
), он должен написать код, подобный этому:С несколькими типами исключений (имеющих общую базу для всей иерархии) вы можете написать:
где классы исключений выглядят так:
При написании библиотеки основное внимание следует уделять простоте ее использования, а не (обязательно) легкости внутренней реализации.
Вы должны ставить под угрозу простоту использования (как будет выглядеть клиентский код) только тогда, когда попытка сделать это правильно в библиотеке является непомерной.
источник
Я согласен с вашими рецензентами и с @utnapistim. Вы можете использовать
system_error
подход, когда реализуете кроссплатформенные вещи, когда некоторые ошибки требуют специальной обработки. Но даже в этом случае это не хорошее решение, а менее злое решение.Еще кое-что. Когда создаете иерархию исключений, не делайте это очень глубоко. Создавайте только те классы исключений, которые могут обрабатываться клиентами. В большинстве случаев я использую только
std::runtime_error
иstd::logic_error
. Я бросаю,std::runtime_error
когда что-то идет не так, и я ничего не могу сделать (пользователь извлекает устройство из компьютера, он забыл, что приложение все еще работает), иstd::logic_error
когда логика программы нарушена (Пользователь пытается удалить запись из базы данных, которая не существует, но перед операцией удаления можете проверить, чтобы он получил логическую ошибку).И, как разработчик библиотеки, подумайте о потребностях ваших пользователей. Попробуйте использовать его сами и подумайте, будет ли это комфортно для вас. Чем вы можете объяснить свою позицию рецензентам с помощью примеров кода.
источник