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

StackOverflow https://stackoverflow.com/questions/196522

Вопрос

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

Это было полезно?

Решение

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

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

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

Преимущества исключений заключаются в двух аспектах:

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

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

Преимущество заключается в том, что вам не нужно проверять код ошибки после каждого потенциально неудачного вызова. Однако, чтобы это работало, вам нужно объединить его с классами 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). Но вы в конечном итоге уделяете меньше внимания тому, где что-то может пойти не так.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top