#c #exception-handling #try-catch
Вопрос:
Я программировал C и C в течение длительного времени и до сих пор никогда не использовал исключения и попытки / уловы. Каковы преимущества использования этого вместо того, чтобы просто иметь функции, возвращающие коды ошибок?
Комментарии:
1. См. Ответ Джеймса Каррана по > 2 >< основным причинам.
Ответ №1:
Возможно, очевидный момент — разработчик может игнорировать (или не знать) о вашем статусе возврата и продолжать пребывать в блаженном неведении о том, что что-то не удалось.
Исключение должно быть каким — то образом признано-его нельзя молча игнорировать, не предпринимая для этого активных действий.
Комментарии:
1. У вас есть один из двух моментов: вы можете легко переместить код для обработки ошибок в нелокальную точку (выше по изменению вызова), которая знает, как справиться с проблемой в более широком контексте.
Ответ №2:
Преимущество исключений состоит в двух аспектах:
- Их нельзя игнорировать. Вы должны иметь с ними дело на каком-то уровне, иначе они завершат вашу программу. С кодом ошибки вы должны явно проверить их наличие, иначе они будут потеряны.
- Их можно игнорировать. если ошибка не может быть устранена на одном уровне, она автоматически перейдет на следующий уровень, где она может быть. Коды ошибок должны быть явно переданы до тех пор, пока они не достигнут уровня, на котором с ними можно будет справиться.
Комментарии:
1. это должно быть лучшим ответом ИМО !
2. не могли бы вы, пожалуйста, уточнить 2 — й пункт (возможно, с примером кода), я не очень хорошо его понимаю-Спасибо.
3. @Xsmael Предположим, что функция funcA() вызывает функцию funcB (), которая вызывает функцию FuncC (), которая вызывает функцию FuncD(). Функция FuncD() может выдавать исключение, и если в funcB или FuncC нет перехватов, оно может быть перехвачено в функции funcA(). Используя код ошибки, FuncC должен принять код от FuncD и, в частности, вернуть его funcB.
4. о, теперь понятно, так что из функции funcA() я могу поймать любое исключение,которое было выдано в любой из вложенных функций B,C, D…. независимо от того, насколько глубоко оно проникает ?
Ответ №3:
Преимущество заключается в том, что вам не нужно проверять код ошибки после каждого потенциально неудачного вызова. Однако для того, чтобы это сработало, вам нужно объединить его с классами RAII, чтобы все автоматически очищалось по мере развертывания стека.
С сообщениями об ошибках:
int DoSomeThings()
{
int error = 0;
HandleA hA;
error = CreateAObject(amp;ha);
if (error)
goto cleanUpFailedA;
HandleB hB;
error = CreateBObjectWithA(hA, amp;hB);
if (error)
goto cleanUpFailedB;
HandleC hC;
error = CreateCObjectWithA(hB, amp;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/исключений кажется длиннее, пока вы не поймете, что код очистки нужно писать только один раз (и есть способы упростить это). Но вторая версия дозиметрии гораздо понятнее и поддается сопровождению.
НЕ пытайтесь использовать исключения в C без идиомы RAII, так как это приведет к утечке ресурсов и памяти. Вся очистка должна выполняться в деструкторах объектов, выделенных стеком.
Я понимаю, что есть и другие способы обработки кода ошибки, но все они в конечном итоге выглядят примерно одинаково. Если вы отбросите goto, вы в конечном итоге повторите код очистки.
Один момент для кодов ошибок заключается в том, что они делают очевидным, где могут произойти сбои и как они могут произойти. В приведенном выше коде вы пишете его, предполагая, что ничего не произойдет (но если это произойдет, вы будете защищены оболочками RAII). Но в конечном итоге вы обращаете меньше внимания на то, где все может пойти не так.
Комментарии:
1. Вы также можете комбинировать RAII и обычные коды ошибок. Оператор return в некоторой степени эквивалентен броску в том отношении, что оба будут вызывать dtor объектов стека, находящихся в области видимости. Так что переходы не нужны.
2. Хорошая мысль. В моей голове коды ошибок всегда для C, поэтому мой мозг не сделал скачка.
Ответ №4:
Обработка исключений полезна, поскольку позволяет легко отделить код обработки ошибок от кода, написанного для обработки функций программы. Это облегчает чтение и написание кода.
Ответ №5:
Помимо прочего, что было упомянуто, вы не можете вернуть код ошибки из конструктора. Деструкторы тоже, но вам также следует избегать создания исключения из деструктора.
Ответ №6:
- возвращайте код ошибки, если в некоторых случаях ожидается состояние ошибки
- выдавать исключение, когда условие ошибки не ожидается ни в каких случаях
в первом случае вызывающий функцию должен проверить код ошибки на ожидаемый сбой; во втором случае исключение может быть обработано любым вызывающим в стеке (или обработчиком по умолчанию), как это необходимо
Ответ №7:
Я написал об этом запись в блоге (Исключения делают для элегантного кода), которая впоследствии была опубликована в Overload. На самом деле я написал это в ответ на то, что сказал Джоэл в подкасте StackOverflow!
В любом случае, я твердо верю, что в большинстве случаев исключения предпочтительнее кодов ошибок. Я нахожу очень болезненным использование функций, возвращающих коды ошибок: вы должны проверять код ошибки после каждого вызова, что может нарушить поток вызывающего кода. Это также означает, что вы не можете использовать перегруженные операторы, так как нет возможности сигнализировать об ошибке.
Боль от проверки кодов ошибок означает, что люди часто пренебрегают этим, что делает их совершенно бессмысленными: по крайней мере, вы должны явно игнорировать исключения с catch
помощью оператора.
Использование деструкторов в C и средств удаления в .NET для обеспечения правильного освобождения ресурсов при наличии исключений также может значительно упростить код. Чтобы получить тот же уровень защиты с кодами ошибок, вам нужно либо много if
операторов, либо много дублированного кода очистки, либо goto
вызовы общего блока очистки в конце функции. Ни один из этих вариантов не является приятным.
Ответ №8:
Вот хорошее объяснение EAFP («Проще попросить прощения, чем разрешения»), которое, я думаю, применимо здесь, даже если это страница на Python в Википедии. Использование исключений приводит к более естественному стилю кодирования, ИМО-и, по мнению многих других, тоже.
Ответ №9:
Когда я преподавал C , нашим стандартным объяснением было то, что они позволяют избежать путаницы в сценариях солнечного и дождливого дней. Другими словами, вы можете написать функцию так, как будто все будет работать нормально, и в конце концов поймать исключение.
Без исключений вам нужно будет получать возвращаемое значение от каждого вызова и убедиться, что оно по-прежнему является законным.
Связанное с этим преимущество, конечно, заключается в том, что вы не «тратите» возвращаемое значение на исключения (и, следовательно, разрешаете методам, которые должны быть недействительными, быть недействительными), а также можете возвращать ошибки от конструкторов и деструкторов.
Комментарии:
1. Выбрасывание исключений из деструкторов широко считается плохой идеей.
Ответ №10:
Руководство Google по стилю C содержит большой, тщательный анализ плюсов и минусов использования исключений в коде C . Это также указывает на некоторые более важные вопросы, которые вы должны задать; т. Е. Собираюсь ли я распространять свой код среди других (у которых могут возникнуть трудности с интеграцией с базой кода с поддержкой исключений)?
Комментарии:
1. Это отличная связь. Интересно отметить, что Google не использует исключений (из-за существующего кода), но что «Все, вероятно, было бы по-другому, если бы нам пришлось все делать заново с нуля».
Ответ №11:
Иногда вам действительно нужно использовать исключение, чтобы отметить исключительный случай. Например, если что-то пойдет не так в конструкторе, и вы обнаружите, что имеет смысл уведомить об этом вызывающего абонента, то у вас нет выбора, кроме как создать исключение.
Другой пример: Иногда нет значения, которое ваша функция может вернуть для обозначения ошибки; любое значение, которое может вернуть функция, означает успех.
int divide(int a, int b)
{
if( b == 0 )
// then what? no integer can be used for an error flag!
else
return a / b;
}
Комментарии:
1. Хороший пример. Другой — это функции обратного вызова, которые вы передаете сторонним библиотекам, над которыми вы не имеете никакого контроля. Обработка исключений также может быть единственным способом устранения ошибки.
Ответ №12:
Тот факт, что вы должны признавать исключения, является правильным, но это также может быть реализовано с помощью структур ошибок. Вы можете создать базовый класс ошибок, который проверяет в своем dtor, был ли вызван определенный метод ( например, isOk ). Если нет, вы можете что-то зарегистрировать, а затем выйти, или создать исключение, или вызвать утверждение и т. Д…
Просто вызов isOk для объекта ошибки, не реагируя на него, был бы эквивалентен написанию catch ( … ) {}. Оба оператора будут демонстрировать одинаковое отсутствие доброй воли программиста.
Большую озабоченность вызывает перенос кода ошибки на правильный уровень. В принципе, вам пришлось бы заставить почти все методы возвращать код ошибки только по причине распространения. Но опять же, функция или метод всегда должны быть аннотированы исключениями, которые они могут генерировать. Таким образом, в основном у вас та же проблема, но без интерфейса для ее поддержки.
Ответ №13:
Как отметил @Martin, выбрасывание исключений заставляет программиста обрабатывать ошибку. Например, отсутствие проверки кодов возврата является одним из самых больших источников дыр в безопасности в программах на языке Си. Исключения убедитесь, что вы обработали ошибку (надеюсь) и предоставили какой-то путь восстановления для вашей программы. И если вы решите игнорировать исключение, а не создавать дыру в системе безопасности, ваша программа выйдет из строя.