Иерархия исключений в стандартной библиотеке C ++ IMHO довольно произвольна и бессмысленна. Например, это, вероятно, только создаст проблемы, если кто-то действительно начнет использовать, например, std::logic_error
вместо завершения, когда становится ясно, что в программе имеется очень неприятная ошибка. Ибо, как гласит стандарт,
«Отличительной характеристикой логических ошибок является то, что они связаны с ошибками во внутренней логике программы».
Таким образом, в тот момент, когда в противном случае может показаться разумным бросить std::logic_error
, состояние программы может быть непредсказуемым образом нарушено, и дальнейшее выполнение может нанести вред пользовательским данным.
Тем не менее, как и std::string
, стандартная иерархия классов исключений имеет действительно очень важную и полезную функцию, а именно то, что она формально стандартна .
Таким образом, любой пользовательский класс исключений должен быть получен косвенно или (хотя я бы не рекомендовал это) напрямую из std::exception
.
Как правило, когда дебаты о пользовательских классах исключений бушевали десять лет назад, я рекомендовал происходить только из std::runtime_error
, и я все еще рекомендую это. Это стандартный класс исключений, который поддерживает пользовательские сообщения (другие обычно имеют жестко закодированные сообщения, которые желательно не изменять, так как они имеют ценность в распознаваемости). И можно утверждать, что std::runtime_error
является стандартным классом исключений, который представляет восстанавливаемые сбои (в отличие от невосстановимых логических ошибок , которые нельзя исправить во время выполнения), или как стандарт гласит:
& ldquo; ошибки времени выполнения происходят из-за событий, выходящих за рамки программы. Они не могут быть легко предсказаны заранее.
Иногда механизм исключений C ++ используется для других вещей, рассматриваемых как просто низкоуровневый механизм динамического перехода назначения. Например, умный код может использовать исключения для распространения успешного результата из цепочки рекурсивных вызовов. Но исключение-как-сбой является наиболее распространенным использованием, и именно для этого обычно оптимизируются исключения C ++, поэтому в большинстве случаев имеет смысл использовать std::runtime_error
в качестве root для любой пользовательской иерархии классов исключений & ndash; даже если это вынуждает того, кто хочет быть умным, генерировать исключение, указывающее на неудачу, чтобы указать на успех
Стоит отметить: есть три стандартных подкласса std::runtime_error
, а именно std::range_error
, std::overflow_error
и std::underflow_error
, и что в отличие от того, что их имена указывают, последние два не обязательно должны генерироваться операциями с плавающей запятой и на практике не генерируются операциями с плавающей запятой, а AFAIK генерируются только некоторыми & ndash; сюрприз! & Ndash; std::bitset
операции. Проще говоря, стандартная иерархия классов исключений библиотеки, как мне кажется, была добавлена туда просто ради внешнего вида, без каких-либо реальных уважительных причин или существующей практики, и даже без проверки «сделай сам». Но, возможно, я упустил это, и если так, то у меня еще есть что-то новое, чтобы узнать об этом. : -)
Итак, std::runtime_error
так и есть.
На вершине иерархии пользовательских классов исключений, в C ++ 03 было полезно добавить важные вещи, отсутствующие в стандартных исключениях C ++ 03:
Virtual clone
метод (особенно важный для передачи исключений через код C).
Виртуальный метод throwSelf
(та же основная причина, что и для клонирования).
Поддержка связанных сообщений об исключениях (стандартизация формата).
Поддержка переноса кода причины сбоя (например, кода ошибки Windows или Posix).
Поддержка получения стандартного сообщения из перенесенного кода причины сбоя.
В C ++ 11 добавлена поддержка большей части этого, но за исключением опробования новой поддержки кодов и сообщений о причинах сбоев, и отмечая, что, к сожалению, она довольно специфична для Unix и не очень подходит для Windows, Я еще не использовал его. В любом случае, для полноты: вместо добавления клонирования и виртуального повторного броска (что является лучшим, что обычный программист приложения может сделать в пользовательской иерархии классов исключений, потому что как программист приложения вы не можете вытащить текущий объект исключения из хранилища, в котором реализация & rsquo ; s использует распространение исключений), стандарт C ++ 11 добавляет свободные функции std::current_exception()
и std::rethrow_exception()
, и вместо поддержки связанных сообщений об исключениях он добавляет класс mixin std::nested_exception
и свободные функции std::rethrow_nested
и std::rethrow_if_nested
.
Учитывая частичную поддержку C ++ 11 для вышеупомянутых пунктов, новая и современная иерархия пользовательских классов исключений должна лучше интегрироваться с поддержкой C ++ 11 вместо устранения недостатков C ++ 03. Ну, за исключением кода ошибки C ++ 11, который кажется очень неподходящим для программирования Windows. Таким образом, в верхней части пользовательской иерархии, прямо под std::runtime_error
, в идеале должен быть хотя бы один общий класс исключений и, как следствие этого, один класс исключений, который поддерживает распространение кодов сбоев.
Теперь, наконец, суть вопроса: лучше ли теперь получить уникальный класс исключений для каждой возможной причины отказа или, по крайней мере, для основных причин отказа?
Я говорю нет: НЕ ДОБАВЛЯЙТЕ НИКАКОЙ СЛОЖНОСТИ .
Если или где это может быть полезно для вызывающего абонента, чтобы отличить определенную причину сбоя, отдельный класс исключений для этого очень полезен. Но в большинстве случаев единственная информация, представляющая интерес для звонящего, - это единственный факт, что произошло исключение. Очень редко разные причины сбоев приводят к различным попыткам исправления.
Но как насчет кодов причин сбоя?
Ну, когда это то, что дает вам базовый API, это просто добавленная работа по созданию соответствующих классов исключений. Но с другой стороны, когда вы сообщаете о сбое в цепочке вызовов, и вызывающему абоненту может потребоваться узнать точную причину, тогда использование кода для этого означает, что вызывающему абоненту придется использовать некоторые вложенные проверки и диспетчеризацию внутри catch
. Таким образом, это разные ситуации: (A) ваш код является исходным источником индикации сбоя, по сравнению с (B) вашим кодом, например. неработающая функция Windows или Posix API, указывающая причину сбоя с помощью кода причины сбоя.
Если условие ошибки - это то, что вызывающая сторона вашей библиотеки могла бы предотвратить, изменив логику своего кода, то выведите свое исключение из logic_error
. Обычно вызывающая сторона не сможет выполнить простую повторную попытку, если выбрано logic_error
. Например, кто-то вызывает ваш код так, что он вызовет деление на 0, тогда вы можете создать пользовательское исключение,
class divide_by_zero : public logic_error {
public:
divide_by_zero(const string& message)
: logic_error(message) {
}
}
Если условие ошибки было чем-то, что не могло быть предотвращено вызывающей стороной, а затем производным от runtime_error
. Некоторые из этих ошибок могут быть исправлены (то есть вызывающая сторона может перехватить исключение, повторить попытку или проигнорировать).
class network_down : public runtime_error {
public:
network_down(const string& message)
: runtime_error(message) {
}
}
Это также общая философия для разработки исключений в стандартной библиотеке. Вы можете просмотреть код исключения для GCC здесь .