Подтвердить что ты не робот

Существует ли общий консенсус в сообществе С++ о том, когда следует использовать исключения?

Я просто потратил несколько часов на чтение SO-вопросов по теме, когда использовать исключения, и кажется, что есть два лагеря с разными точками зрения:

  • Использовать исключения по кодам ошибок
  • Использовать коды ошибок большую часть времени и исключения только в случае возникновения какой-либо катастрофической ошибки.

Это просто спорная тема, в которой нет широко распространенной передовой практики?

4b9b3361

Ответ 1

Как вы, вероятно, можете собрать из множества ответов, безусловно, нет консенсуса.

Семантически исключения и ошибки обеспечивают точно такую ​​же функциональность. Действительно, они идентичны во всех семантических аспектах, и ошибки могут быть произвольно обогащены подобно исключениям (вам не нужно использовать простой код, вы можете использовать реальный набор данных!).

Единственное различие заключается в их методах распространения:

  • ошибки должны быть переданы вручную
  • автоматически распространяются исключения

С другой стороны:

  • возможность ошибки прекрасно документирована в сигнатуре
  • исключения молчат при проверке кода (читайте GotW # 20: сложность кода и крик), а скрытые пути выполнения усложняют работу.

Сама причина, по которой оба решения могут казаться неуклюжими, - это просто проверка ошибок. На самом деле большая часть кода, который я ежедневно пишу, касается проверки ошибок, будь то технические или функциональные.

Итак, что делать?

Предупреждение: демонстрация впереди, переход к следующему разделу, если вы заботитесь только о ответе

Мне лично нравится использовать систему типов здесь. Типичным примером является дихотомия с указателем-ссылкой: указатель похож на ссылку, которая может быть нулевой (и обновлена, но здесь это не имеет значения)

Следовательно, вместо:

// Exceptions specifications are better not used in C++
// Those here are just to indicate the presence of exceptions
Object const& Container::search(Key const& key) const throw(NotFound);

Я буду писать:

Object const* Container::search(Key const& key) const;

Или еще лучше, используя умные указатели:

Pointer<Object const> Container::search(Key const& key) const;

template <typename O>
O* Pointer<O>::operator->() const throw(Null);

template <typename O>
O& Pointer<O>::operator*() const throw(Null);

Здесь я нахожу использование исключения лишним по двум причинам:

  • Если мы ищем объект, то не обнаруживаем, что это как совершенно обычное явление, так и не так много данных: причина ошибки? его там нет
  • Клиент не обязательно считает ошибкой, что его нет, кому я должен предположить, что я знаю ее бизнес лучше, чем она? Кто я такой, чтобы решить, что никогда не будет случая, когда не будет нужным найти то, что было предложено?

У меня нет проблем с исключениями как таковыми, но они могут сделать код неудобным, считают:

void noExceptions(Container const& c)
{
  Pointer<Object const> o = c.search("my-item");

  if (!o) {
    o = c.search("my-other-item");
  }

  if (!o) { return; } // nothing to be done

  // do something with o
}

И сравните его со случаем "исключение":

void exceptions(Container const& c)
{
  Object const* p = 0;
  try {
    p = &c.search("my-item");
  }
  catch(NotFound const&) {
    try {
      p = &c.search("my-other-item");
    }
    catch(NotFound const&) {
      return; // nothing to be done
    }
  }

  // do something with p
}

В этом случае использование исключений не представляется целесообразным:/

С другой стороны:

try {
 print() << "My cute little baby " << baby.name() << " weighs " << baby.weight();
}
catch(Oupsie const&) {
  // deal
}

безусловно, более привлекателен, чем:

if (!print("My cute little baby ")) { /*deal*/ }
if (!print(baby.name())) { /*deal*/ }
if (!print(" weighs ")) { /*deal*/ }
if (!print(baby.weight())) { /*deal*/ }

Что лучше всего?

Это зависит. Как и вся инженерная проблема, нет серебряной пули, все о концессиях.

Итак, помните о двух вещах:

  • Отчет об ошибках является частью API
  • API должны быть разработаны с учетом удобства использования.

Если вам интересно, следует ли использовать исключение или нет, просто попробуйте использовать свой API. Если нет четкого победителя, это просто: идеального решения нет.

О, и не стесняйтесь реорганизовать свой API, когда станет ясно, что механизм отчетности об ошибках, избранный во время его создания, больше не подходит. Не стесняйтесь: требования меняются со временем, поэтому нормально, что API меняется с ними.

Лично я предпочитаю использовать исключения только для невосстановимых ошибок: поэтому у меня есть несколько попыток /catch в моем коде, только на самых внешних уровнях, чтобы точно регистрировать ошибку (любить стековые фреймы) и регистрировать дамп спецификации.

Это очень похожее (и действительно сильно влияет) на Haskell, код разделяется на две части с четким разрезом: в то время как любые могут делать исключения, только часть IO (внешняя) может их поймать. Поэтому чистая часть должна иметь дело с условиями ошибки другими способами, если они "нормальны".

Если, однако, я столкнулся с проблемой, когда использование исключения делает код более легким для чтения и более естественным (что субъективно), то я использую исключение:)

Ответ 2

Я не думаю, что это обсуждение, которое является эксклюзивным для сообщества С++, но вот два руководства высокого уровня, которые помогли мне:

  • Выбрасывать исключения только в исключительных обстоятельствах. Это кажется очевидным, но многие API-интерфейсы создаются с исключениями, которые генерируются примерно в 50% случаев, когда они вызываются (и более подходящим было бы состояние возврата в булевом режиме).
  • В ваших предложениях catch, знайте, когда потреблять исключения, когда восстанавливать их как есть и когда вместо этого следует использовать другой тип исключения. Я не могу дать вам правило одного размера для всех, потому что он так зависит от потребностей вашего приложения, но если вы от него уйдете, то должно быть, что молчаливое потребление исключения может быть худшим ваш код мог бы сделать. Каждый кадр должен иметь некоторое представление о том, что ожидающий кадр ожидает, когда что-то пойдет не так.

Ответ 3

Исключения легче использовать, чем коды ошибок, потому что они могут быть брошены глубоко вложенной подпрограммой и перехвачены только на том уровне, на котором можно обрабатывать.

Коды ошибок должны быть переданы по цепочке, поэтому каждая функция, вызывающая другую, должна передать код ошибки обратно своим вызывающим абонентам.

Код ошибки не имеет явных функциональных преимуществ перед исключениями, поскольку исключение может связывать код ошибки внутри него. Возможно, было время, когда коды ошибок могли быть более эффективными, чем исключения, но я думаю, что стоимость дополнительного кода и трудности с его сохранением перевешивают любое возможное преимущество.

Однако коды ошибок существуют во многих приложениях, потому что они записаны или были перенесены с языка, на котором не было исключений, поэтому имеет смысл продолжать использовать единообразный подход к обработке ошибок.

Ответ 4

Нет, консенсуса нет.

Изменить: как вы можете видеть, из других ответов нет консенсуса - только школы мысли, принципы дизайна и планы действий. Ни один план не подходит для любой ситуации.

Ответ 5

Даже если бы было общее согласие, это не значит, что оно действительно. Ваше основное внимание здесь должно быть связано с реализацией и производительностью. Это как реальные ограничения для проекта, так и итоговой программы. В этом отношении рассмотрим некоторые вещи.

Во время выполнения исключения дороже распространяться, чем простые возвращаемые значения. Обычно это источник аргумента в исключительных случаях. Если вы бросаете много исключений все время, когда вы страдаете штрафом за выполнение. Не думайте, что это означает, что исключения медленны, они все еще реализованы очень эффективно, но, тем не менее, дороже, чем коды ошибок.

Исключения могут содержать больше информации, чем коды ошибок. Что-то вроде библиотеки boost::exception позволяет помечать информацию, которая может предоставить полезную информацию в цепочке исключения. В отличие от простого кода ошибки, говорящего, что файл не найден, исключение может содержать имя файла, модуль, который пытался его загрузить, и, конечно, основной код ошибки. Этот тип информации очень трудно распространять по кодам ошибок в стеке.

Прохождение кода ошибки может быть громоздким во время реализации. Любая функция, которая не хочет иметь дело с ошибкой, должна передать значение выше. Довольно часто вы найдете код, который просто игнорирует ошибку, поскольку программист не мог справиться с этим в то время, или он не захотел реорганизовать свои сигнатуры функций для прохождения по ошибке.

Исключение catch синтаксис громоздкий. Часто бывает гораздо проще проверить код возврата в выражении if, чем писать блок catch. Если функция должна улавливать слишком много разных исключений в разных точках, смысл кода будет потерян в море фигурных скобок и предложения try/catch. Вот вероятный корень представления о том, что если вызов функции может нормально завершиться, код возврата, скорее всего, лучше исключения.

Понимание того, как работают исключения, поможет вам принять решение. Есть соображения производительности, но для многих проектов это будет незначительным. Есть опасения относительно безопасности исключений, вы должны знать, где безопасно и небезопасно использовать исключения. Коды ошибок также имеют одинаковую проблему с безопасностью, если люди начинают делать короткие возвращения от функций.

Понимание фона вашей команды также может помочь; подумайте, есть ли у вас два новых программиста на С++, один из C-фона, а другой - из Java. Некоторые люди более удобны с кодами ошибок, другие с исключениями. Как далеко вы продвигаетесь в каждом направлении, это повлияет на ваш проект и будет способствовать общему качеству.

В конце нет четкого ответа. Хотя есть ситуации, когда человек может явно победить другого, он сильно зависит от проекта. Посмотрите на свой код и продолжайте рефакторинг, когда это необходимо. Иногда у вас даже не будет выбора относительно того, что вы используете. В других случаях это не имеет значения. Это очень специфично для проекта.

Ответ 6

Скорее всего, нет консенсуса. На высоком уровне моя мантра заключается в том, что исключения должны использоваться в "исключительных случаях", то есть случаях, которые не являются результатом ошибки программиста, а непредсказуемых условий в среде выполнения.

Есть много дискуссий вокруг этой проблемы. В зависимости от других практик, которыми вы занимаетесь при написании программ - например, RAII - исключения могут быть более или менее полезными для идиоматики.

Ответ 7

Исключения - это только способ правильно сообщать о ошибках конструктора, поэтому выбор там ясен. Выбрасывание исключения заставляет вызывающего его поймать и обработать его каким-то образом. Альтернативный дизайн пустого конструктора с методом init(), возвращающим код ошибки, является подверженным ошибкам и может привести к объектам с неизвестным состоянием.

Ответ 8

"Сообщество С++" имеет фракции, каждый из которых имеет свое собственное мнение об исключениях.

Где я работаю в индустрии видеоигр, исключения запрещены в коде, который работает в системе видеоигр. Это связано с тем, что в исключениях используется либо процессорное время OR, либо никакая из которых система видеоигр не может сэкономить.

Кроме того, в видеоиграх есть несколько случаев сбоев, которые гарантируют изящное восстановление; Если видеоигр терпит неудачу, человеческие жизни или собственность обычно не поставлены на карту.

Ответ 10

Нет консенсуса, но, как показывают ответы, многие придерживаются мнения о том, чтобы бросить исключение только в исключительных обстоятельствах. Мне не очень нравится этот совет, потому что тогда вопрос становится "что такое исключительное обстоятельство?". Я предпочитаю следующий совет

Функция должна выдавать исключение, если и только в том случае, если альтернативой будет невыполнение пост-состояния (включая все инварианты как неявные пост-условия).

Реализация реализации того, писать ли throw в вашем коде, затем привязана к проектным решениям пост-условий для функции.

См. Стандарты кодирования С++ Херба Саттера и Андрея Александреску, пункты 70 (Различают ошибки и ошибки) и 72 (Предпочитают использовать исключения для сообщений об ошибках).

Ответ 11

Я не рассматривал это в каких-либо других ответах, но (как описано в книге "Эффективная Java" ) при выполнении ООП, возможность пропуска исключений бросания состоит в том, чтобы иметь вспомогательный метод в объекте, чтобы "спросить", возможно выполнение операции.

Например, метод hasNext() на Iterator или Scanner, который обычно вызывается перед вызовом соответствующего следующего метода *().