Во- первых, как уже говорилось, все не что ясна в C ++, ИМХО в основном потому , что требования и ограничения являются несколько более варьировалась в C ++ , чем другие языки, особ. C # и Java, которые имеют "похожие" проблемы исключений.
Я покажу на примере std :: stof:
передача пустой строки в std :: stof (сгенерирует invalid_argument) не является ошибкой программирования
Основной контракт , на мой взгляд, этой функции заключается в том, что она пытается преобразовать свой аргумент в число с плавающей запятой, и любой отказ сделать это сообщается исключением. Оба возможных исключения являются производными, logic_error
но не в смысле ошибки программиста, а в смысле «входные данные никогда не могут быть преобразованы в число с плавающей запятой».
Здесь можно сказать, что logic_error
используется, чтобы указать, что, учитывая, что (во время выполнения) ввод, всегда пытаться преобразовать его - ошибка, но задача функции - определить это и сообщить вам (через исключение).
Примечание: в этом представлении a runtime_error
можно рассматривать как нечто, что при одинаковом входе в функцию теоретически может быть успешным для разных прогонов. (например, файловая операция, доступ к БД и т. д.)
Дополнительное примечание: библиотека регулярных выражений C ++ выбрала свою ошибку, runtime_error
хотя бывают случаи, когда ее можно классифицировать так же, как здесь (недопустимый шаблон регулярных выражений).
Это просто показывает, IMHO, что группировка logic_
или runtime_
ошибка довольно нечетки в C ++ и не очень помогают в общем случае (*) - если вам нужно обрабатывать конкретные ошибки, вам, вероятно, нужно отлавливать меньше, чем две.
(*): Это не означает , что один кусок кода не должен быть последовательным, но будет ли вы бросить runtime_
или logic_
или custom_
нечто действительно не так уж важно, как мне кажется.
Комментировать оба stof
и bitset
:
Обе функции принимают строки в качестве аргумента, и в обоих случаях это так:
- нетривиально, чтобы проверить для вызывающей стороны, является ли данная строка допустимой (например, в худшем случае вам придется копировать логику функции; в случае набора битов не сразу ясно, является ли пустая строка допустимой, поэтому пусть решает ctor)
- Функция уже несет ответственность за «синтаксический анализ» строки, поэтому она уже должна проверить строку, поэтому имеет смысл, что она сообщает об ошибке, чтобы «использовать» строку равномерно (и в обоих случаях это исключение) ,
Правило, которое часто встречается с исключениями: «Используйте исключения только в исключительных обстоятельствах». Но как библиотечная функция должна знать, какие обстоятельства являются исключительными?
Это утверждение имеет, ИМХО, два корня:
Производительность : если функция вызывается по критическому пути, и «исключительный» случай не является исключительным, т. Е. Значительное количество проходов будет связано с созданием исключения, то платить каждый раз за механизм исключения-разматывания не имеет смысла и может быть слишком медленным.
Местность обработки ошибок : Если функция вызывается и исключение сразу пойманы и обработаны, то есть мало смысла бросать исключение, так как обработка ошибок будет более многословным сcatch
, чем с if
.
Пример:
float readOrDefault;
try {
readOrDefault = stof(...);
} catch(std::exception&) {
// discard execption, just use default value
readOrDefault = 3.14f; // 3.14 is the default value if cannot be read
}
Вот где такие функции, как TryParse
vs., Parse
вступают в игру: одна версия, когда локальный код ожидает, что проанализированная строка является действительной, одна версия, когда локальный код предполагает, что на самом деле ожидается (то есть, не является исключительным), что синтаксический анализ завершится неудачно.
На самом деле, stof
это просто (определяется как) оболочка strtof
, поэтому, если вы не хотите исключений, используйте это.
Итак, как я должен решить, следует ли мне использовать исключения или нет для конкретной функции?
ИМХО, у вас есть два случая:
Функция, подобная «библиотеке» (часто используемая в разных контекстах): Вы в принципе не можете решить. Возможно, предоставьте обе версии, возможно ту, которая сообщает об ошибке, и обертку, которая преобразует возвращенную ошибку в исключение.
Функция «приложение» (специфичная для большого количества кода приложения, может быть использована повторно, но ограничена стилем обработки ошибок приложений и т. Д.): Здесь она часто должна быть достаточно четкой. Если пути кода, вызывающие функции, обрабатывают исключения разумным и полезным способом, используйте исключения, чтобы сообщить о любой (но см. Ниже) ошибке. Если код приложения легче читать и писать для стиля возврата ошибки, обязательно используйте его.
Конечно, между ними будут места - просто используйте то, что нужно, и помните YAGNI.
Наконец, я думаю, что я должен вернуться к утверждению FAQ,
Не используйте throw, чтобы указать на ошибку кодирования при использовании функции. Используйте assert или другой механизм для отправки процесса в отладчик или для сбоя процесса ...
Я подписываюсь на это для всех ошибок, которые являются явным признаком того, что что-то серьезно испорчено или что вызывающий код явно не знал, что он делал.
Но когда это уместно, это часто сильно зависит от приложения, поэтому смотрите выше домен библиотеки или домен приложения.
Это возвращает нас к вопросу о том, нужно ли и как проверять предварительные условия вызова , но я не буду вдаваться в подробности, отвечу уже слишком долго :-)