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

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

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

4b9b3361

Ответ 1

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

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

Ответ 2

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

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

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

Ответ 3

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

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

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

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

Ответ 4

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

Ответ 5

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

Ответ 6

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

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

Ответ 7

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

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

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

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

Ответ 9

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

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

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

Ответ 10

Руководство по стилю 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;
}

Ответ 12

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

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

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

Ответ 13

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