Современные рекомендации по C++ по исключению и обработке ошибок
В современных C++в большинстве сценариев предпочтительный способ сообщать об ошибках логики и обработать ошибки логики и ошибки среды выполнения — использовать исключения. Это особенно верно, если стек может содержать несколько вызовов функций между функцией, которая обнаруживает ошибку, и функцию, которая имеет контекст для обработки ошибки. Исключения предоставляют формальный, четко определенный способ для кода, который обнаруживает ошибки для передачи информации в стек вызовов.
Использование исключений для исключительного кода
Ошибки программы часто делятся на две категории:
- Ошибки логики, вызванные ошибками программирования. Например, ошибка "индекс вне диапазона".
- Ошибки среды выполнения, которые выходят за рамки контроля программиста. Например, ошибка "сетевая служба недоступна".
В программировании на языке C и com отчеты об ошибках управляются либо путем возврата значения, представляющего код ошибки или код состояния для конкретной функции, либо путем задания глобальной переменной, которую вызывающий объект может при необходимости получить после каждого вызова функции, чтобы узнать, были ли сообщения об ошибках. Например, com-программирование использует HRESULT
возвращаемое значение для передачи ошибок вызывающей объекту. А API Win32 имеет GetLastError
функцию для получения последней ошибки, сообщаемой стеком вызовов. В обоих случаях вызывающий объект распознает код и отвечает на него соответствующим образом. Если вызывающий объект не обрабатывает код ошибки явным образом, программа может завершиться без предупреждения. Кроме того, он может продолжать выполняться с использованием плохих данных и создавать неправильные результаты.
Исключения предпочтительны в современном C++ по следующим причинам:
- Исключение заставляет вызывающий код распознавать условие ошибки и обрабатывать его. Необработанные исключения останавливают выполнение программы.
- Исключение переходит к точке стека вызовов, которая может обрабатывать ошибку. Промежуточные функции могут позволить распространению исключения. Они не должны координироваться с другими слоями.
- Механизм очистки стека исключений уничтожает все объекты в области после создания исключения в соответствии с четко определенными правилами.
- Исключение позволяет четко разделить код, который обнаруживает ошибку и код, обрабатывающий ошибку.
В следующем упрощенном примере показан необходимый синтаксис для создания и перехвата исключений в C++:
#include <stdexcept>
#include <limits>
#include <iostream>
using namespace std;
void MyFunc(int c)
{
if (c > numeric_limits< char> ::max())
{
throw invalid_argument("MyFunc argument too large.");
}
//...
}
int main()
{
try
{
MyFunc(256); //cause an exception to throw
}
catch (invalid_argument& e)
{
cerr << e.what() << endl;
return -1;
}
//...
return 0;
}
Исключения в C++ похожи на такие языки, как C# и Java. В блокеtry
, если исключение создается, он перехватывается первым связанным catch
блоком, тип которого соответствует типу исключения. Другими словами, выполнение переходит от инструкции throw
к оператору catch
. Если не найден доступный блок перехвата, std::terminate
вызывается и программа завершает работу. В C++любой тип может быть создан; однако рекомендуется создать тип, производный напрямую или косвенно от std::exception
. В предыдущем примере тип invalid_argument
исключения определен в стандартной библиотеке в файле заголовка <stdexcept>
. C++ не предоставляет или не требует finally
блока, чтобы убедиться, что все ресурсы освобождены, если исключение возникает. Приобретение ресурсов — идиома инициализация (RAII), которая использует интеллектуальные указатели, предоставляет необходимые функциональные возможности для очистки ресурсов. Дополнительные сведения см. в разделе "Практическое руководство . Проектирование для обеспечения безопасности исключений". Сведения о механизме очистки стека C++ см. в разделе "Исключения" и очистка стека.
Основные рекомендации
Надежная обработка ошибок сложна в любом языке программирования. Хотя исключения предоставляют несколько функций, поддерживающих хорошую обработку ошибок, они не могут выполнять всю работу. Чтобы реализовать преимущества механизма исключений, следует учитывать исключения при разработке кода.
- Используйте утверждения для проверки условий, которые всегда должны быть верными или всегда быть ложными. Используйте исключения, чтобы проверить наличие ошибок, которые могут возникать, например, ошибки при проверке входных данных для параметров общедоступных функций. Дополнительные сведения см. в разделе "Исключения и утверждения".
- Используйте исключения, если код, обрабатывающий ошибку, отделен от кода, который обнаруживает ошибку одним или несколькими промежуточными вызовами функций. Рассмотрим, следует ли использовать коды ошибок вместо циклов, критически важных для производительности, когда код, обрабатывающий ошибку, тесно связан с кодом, который обнаруживает его.
- Для каждой функции, которая может вызывать или распространять исключение, предоставьте одну из трех гарантий исключения: надежную гарантию, базовую гарантию или гарантию нотхроу (
noexcept
). Дополнительные сведения см. в разделе "Практическое руководство . Проектирование для обеспечения безопасности исключений". - Создает исключения по значению, перехватывает их по ссылке. Не перехватывать то, что вы не можете обрабатывать.
- Не используйте спецификации исключений, которые устарели в C++11. Дополнительные сведения см. в разделе спецификаций исключений и
noexcept
раздела. - Используйте стандартные типы исключений библиотеки при их применении. Производные пользовательские типы исключений
exception
из иерархии классов . - Не разрешайте исключениям экранироваться от деструкторов или функций размещения памяти.
Исключения и производительность
Механизм исключения имеет минимальные затраты на производительность, если исключение не возникает. Если исключение возникает, стоимость обхода стека и очистки примерно сравнима с стоимостью вызова функции. Другие структуры данных необходимы для отслеживания стека вызовов после try
ввода блока, а дополнительные инструкции требуются для очистки стека при возникновении исключения. Однако в большинстве случаев затраты на производительность и объем памяти не являются значительными. Негативное влияние исключений на производительность, скорее всего, будет значительным только в системах с ограниченными памятью. Или, в критически важных для производительности циклах, когда ошибка, скорее всего, возникает регулярно, и между кодом для обработки кода и кода, который сообщает об этом. В любом случае невозможно знать фактическую стоимость исключений без профилирования и измерения. Даже в тех редких случаях, когда стоимость значительна, вы можете взвесить его по отношению к повышенной правильности, простоте обслуживания и другим преимуществам, предоставляемым хорошо разработанной политикой исключений.
Исключения и утверждения
Исключения и утверждения являются двумя различными механизмами обнаружения ошибок во время выполнения в программе. Используйте assert
инструкции для проверки условий во время разработки, которые всегда должны быть истинными или всегда быть ложными, если весь код правильный. Нет смысла обрабатывать такую ошибку с помощью исключения, так как ошибка указывает, что что-то в коде должно быть исправлено. Это не означает условие, которое программа должна восстановить во время выполнения. При остановке assert
выполнения инструкции можно проверить состояние программы в отладчике. Исключение продолжает выполнение из первого соответствующего обработчика перехвата. Используйте исключения для проверки условий ошибки, которые могут возникнуть во время выполнения, даже если код исправлен, например "файл не найден" или "вне памяти". Исключения могут обрабатывать эти условия, даже если восстановление просто выводит сообщение в журнал и заканчивает программу. Всегда проверяйте аргументы для общедоступных функций с помощью исключений. Даже если функция не является ошибкой, возможно, у вас нет полного контроля над аргументами, которые пользователь может передать в него.
Исключения C++ и исключения SEH Для Windows
Программы C и C++ могут использовать структурированный механизм обработки исключений (SEH) в операционной системе Windows. Основные понятия в SEH похожи на те, которые используются в исключениях C++, за исключением того, что SEH использует __try
__except
__finally
и конструкции, а не .try
catch
В компиляторе Microsoft C++ (MSVC) исключения C++ реализуются для SEH. Однако при написании кода C++ используйте синтаксис исключения C++.
Дополнительные сведения о SEH см. в разделе "Структурированная обработка исключений" (C/C++).
Спецификации исключений и noexcept
Спецификации исключений были представлены в C++ в качестве способа указания исключений, которые может вызывать функция. Однако спецификации исключений оказались проблематичными на практике и устарели в стандарте черновика C++11. Рекомендуется не использовать throw
спецификации исключений, за исключением того throw()
, что функция не позволяет экранировать исключения. Если необходимо использовать спецификации исключений устаревшей формы throw( type-name )
, поддержка MSVC ограничена. Дополнительные сведения см. в разделе "Спецификации исключений (throw)". Описатель noexcept
представлен в C++11 в качестве предпочтительной альтернативы throw()
.
См. также
Практическое руководство. Интерфейс между исключительным и неисключаемого кода
Справочник по языку C++
Стандартная библиотека C++