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

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

Ответы [ 13 ]

28 голосов
/ 13 октября 2008

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

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

22 голосов
/ 13 октября 2008

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

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

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

17 голосов
/ 13 октября 2008

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

10 голосов
/ 13 октября 2008

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

8 голосов
/ 13 октября 2008

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

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

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

4 голосов
/ 13 октября 2008

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

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

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

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

2 голосов
/ 10 декабря 2008

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

2 голосов
/ 13 октября 2008

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

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

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

2 голосов
/ 13 октября 2008

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

...