В C++ каковы преимущества использования исключений и try / catch вместо простого возврата кода ошибки?

Я программировал на C и C++ долгое время и до сих пор никогда не использовал исключения и try / catch. Каковы преимущества использования этого вместо того, чтобы функции возвращали коды ошибок?

См. Ответ Джеймса Каррана по основным причинам > 2 <.

Martin York 10.12.2008 08:13
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
25
1
11 766
13
Перейти к ответу Данный вопрос помечен как решенный

Ответы 13

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

  • вернуть код ошибки, когда условие ошибки - ожидал в некоторых случаях
  • генерировать исключение, когда условие ошибки нет ожидается в любых случаях

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

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

С сообщениями об ошибках:

int DoSomeThings()
{
    int error = 0;
    HandleA hA;
    error = CreateAObject(&ha);
    if (error)
       goto cleanUpFailedA;

    HandleB hB;
    error = CreateBObjectWithA(hA, &hB);
    if (error)
       goto cleanUpFailedB;

    HandleC hC;
    error = CreateCObjectWithA(hB, &hC);
    if (error)
       goto cleanUpFailedC;

    ...

    cleanUpFailedC:
       DeleteCObject(hC);
    cleanUpFailedB:
       DeleteBObject(hB);
    cleanUpFailedA:
       DeleteAObject(hA);

    return error;
}

С исключениями и RAII

void DoSomeThings()
{
    RAIIHandleA hA = CreateAObject();
    RAIIHandleB hB = CreateBObjectWithA(hA);
    RAIIHandleC hC = CreateCObjectWithB(hB);
    ...
}

struct RAIIHandleA
{
    HandleA Handle;
    RAIIHandleA(HandleA handle) : Handle(handle) {}
    ~RAIIHandleA() { DeleteAObject(Handle); }
}
...

На первый взгляд версия RAII / Exceptions кажется длиннее, пока вы не поймете, что код очистки нужно писать только один раз (и есть способы упростить это). Но вторая версия DoSomeThings намного понятнее и удобнее.

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

Я понимаю, что есть и другие способы обработки кода ошибки, но все они выглядят примерно одинаково. Если вы отбрасываете gotos, вы в конечном итоге повторяете код очистки.

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

Вы также можете комбинировать RAII и обычные коды ошибок. Оператор return в некоторой степени эквивалентен throw в том смысле, что оба будут вызывать dtors объектов стека, которые находятся в области видимости. Так что никаких goto не требуется.

QBziZ 13.10.2008 13:46

Хорошая точка зрения. В моей голове коды ошибок всегда для C, поэтому мой мозг не совершил скачок.

Eclipse 13.10.2008 19:30

Вот - хорошее объяснение EAFP («Проще просить прощения, чем разрешения»), которое, я думаю, применимо здесь, даже если это страница Python в Википедии. Использование исключений приводит к более естественному стилю кодирования, IMO - и, по мнению многих других, тоже.

Ответ принят как подходящий

Возможно, это очевидный момент - разработчик может игнорировать (или не знать) ваш статус возврата и продолжать блаженно не осознавать, что что-то не удалось.

Исключение необходимо каким-то образом признать - его нельзя игнорировать, не приняв для этого никаких мер.

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

Martin York 10.12.2008 08:12

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

Как отметил @Martin, выброс исключений заставляет программиста обрабатывать ошибку. Например, непроверка кодов возврата - один из самых больших источников дыр в безопасности в программах на языке C. Исключения гарантируют, что вы обработаете ошибку (надеюсь) и предоставите какой-то путь восстановления для вашей программы. И если вы решите игнорировать исключение, а не создавать брешь в безопасности, ваша программа выйдет из строя.

Преимущество исключений двоякое:

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

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

это должен быть лучший ответ ИМО!

Gob00st 10.01.2012 19:09

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

Xsmael 23.03.2017 21:09

@Xsmael Предположим, что FuncA () вызывает FuncB (), которая вызывает FuncC (), которая вызывает FuncD (). FuncD () может генерировать исключение, и если в FuncB или FuncC нет перехватов, его можно перехватить с помощью FuncA (). Используя код ошибки, FuncC должен принять код от FuncD и, в частности, вернуть его в FuncB.

James Curran 24.03.2017 10:52

о, теперь посмотрим, поэтому из FuncA () я могу поймать любое исключение, которое было выброшено в любой из вложенных функций B, C, D .... независимо от того, насколько глубоко оно заходит?

Xsmael 24.03.2017 18:25

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

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

int divide(int a, int b)
{
    if ( b == 0 )
        // then what?  no integer can be used for an error flag!
    else
        return a / b;
}

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

QBziZ 13.10.2008 13:49

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

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

Связанное с этим преимущество, конечно же, заключается в том, что вы не «тратите» свое возвращаемое значение на исключения (и, таким образом, позволяете методам, которые должны быть недействительными, быть недействительными), а также можете возвращать ошибки из конструкторов и деструкторов.

Выбрасывание исключений из деструкторов считается плохой идеей.

Kerri Brown 09.01.2014 15:31

Я написал об этом запись в блоге (Исключения делают Elegant Code), которая впоследствии была опубликована в Перегрузка. На самом деле я написал это в ответ на то, что Джоэл сказал в подкасте StackOverflow!

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

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

Использование деструкторов в C++ и средств удаления в .NET для обеспечения правильного освобождения ресурсов при наличии исключений также может значительно упростить код. Чтобы получить такой же уровень защиты с кодами ошибок, вам потребуется либо множество операторов if, либо много дублированного кода очистки, либо вызовы goto к общему блоку очистки в конце функции. Ни один из этих вариантов не нравится.

Тот факт, что вы должны подтверждать исключения, является правильным, но это также можно реализовать с помощью структур ошибок. Вы можете создать базовый класс ошибок, который проверяет в своем dtor, был ли вызван определенный метод (например, IsOk). Если нет, вы можете зарегистрировать что-то, а затем выйти, или выбросить исключение, или поднять утверждение и т. д.

Тогда простой вызов IsOk для объекта ошибки без реакции на него будет эквивалентен записи catch (...) {} Оба утверждения демонстрируют одно и то же отсутствие у программиста доброй воли.

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

Руководство по стилю C++ от Google содержит подробный анализ плюсов и минусов использования исключений в коде C++. Он также указывает на некоторые из более важных вопросов, которые вам следует задать; т.е. собираюсь ли я распространять свой код среди других (у кого могут возникнуть трудности с интеграцией с базой кода с включенными исключениями)?

Это отличная ссылка. Интересно отметить, что Google не использует исключения (из-за существующего кода), но что «все могло бы быть иначе, если бы нам пришлось делать это снова и снова с нуля».

dog44wgm 06.02.2012 02:28

Другие вопросы по теме