Обработка ошибок и исключений (современный C++)
В современном C++ в большинстве случаев предпочтительный способ уведомления и обработки логических ошибок и ошибок среды выполнения — использование исключений. Это особенно заметно, когда стек может содержать несколько вызовов функции между функцией, которая обнаруживает ошибку и функцией, которая содержит контекст для получения сведений о его обработки. Исключения предоставляют формальный, четко определенный способ для передачи обнаружившим ошибку кодом сведений вверх по стеку вызовов.
Ошибки программы обычно делятся на две категории: логические ошибки, вызванные ошибками программирования (ошибка "индекс вне диапазона"), а также ошибки среды выполнения, которые не может контролировать программист, например ошибка "сетевая служба недоступна". В стиле программирования C и в модели COM отчеты об ошибках управляются возвратом значения, представляющего код ошибки или код статуса для определенной функции, или заданием глобальной переменной, которая может дополнительно извлекаться вызывающим объектом после каждого вызова функций, чтобы посмотреть, был ли составлен отчет об ошибках. Например, при программировании с использованием модели COM возвращаемое значение HRESULT используется для сообщения об ошибках вызывающему объекту, а в API Win32 есть функция GetLastError для извлечения последней ошибки, о которой сообщил стек вызовов. В обоих этих случаях вызывающий объект решает, признать ли код и нужно ли ответить на него соответственно. Если вызывающий код явно не обрабатывает ошибки, программа может выполнить сбой без предупреждения, либо продолжить выполнение с некорректными данными и привести к неверным результатам.
Исключения являются предпочтительными в современном языке C++ по следующим причинам:
Исключение вынуждает вызывающий код признать состояние ошибки и обработать его. Необработанные исключения останавливают выполнение программы.
Исключение перескакивает в точку в стеке вызовов, которая способна обработать ошибку. Промежуточные функции могут разрешить распространение исключения. Они не должны в соответствии с другими уровнями.
Механизм освобождения стека исключения уничтожает все объекты в области в соответствии с правилами чётким после создания исключения.
Исключение обеспечивает четкое разделение между кодом, который обнаруживает ошибку, и кодом, который обрабатывает ошибку.
В следующем примере показан необходимый упрощенный синтаксис для вызова и перехват исключений в языке C++.
#include <stdexcept>
#include <limits>
#include <iostream>
using namespace std;
class MyClass
{
public:
void MyFunc(char 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. Если годный к использованию блок catch не найден, то вызывается std::terminate и программа выполняет выход. В C++ может быть создан любой тип, однако рекомендуется создать тип, производный непосредственно или косвенно от std::exception. В предыдущем примере тип исключения — invalid_argument — определен в стандартной библиотеке в файле заголовка <stdexcept>. ++ не предоставляет (и не требует) блок finally для гарантированного освобождения всех ресурсов в случае возникновения исключения. Получение ресурса является идиомой инициализации (RAII), который использует интеллектуальные указатели, предоставляет необходимую функциональность для очистки ресурсов. Для получения дополнительной информации см. Практическое руководство. Разработка с учетом безопасности исключений. Сведения о механизме развертывания стека C++ см. в разделе Исключения и освобождение стека в C++.
Основные рекомендации
Надежная обработка ошибок сложна на любом языке программирования. Хотя исключения предоставляют несколько функций, которые поддерживают корректную обработку ошибок, они не могут сделать всю работу за вас. Чтобы реализовать преимущества механизма исключения, помните об исключениях при создании кода.
Использование утверждений для проверки на наличие ошибок, которые никогда не возникают. Используйте исключения для проверки ошибок, которые могут произойти, например, ошибки при проверке ввода для открытых параметрах функций. Для получения дополнительных сведений см. раздел Исключения и утверждения.
Используйте исключения, если код обработки ошибки может быть отделен от кода, который обнаруживает ошибку одним или несколькими не вызовы функций. В циклах, имеющих критическое значение для производительности, когда обрабатывающий ошибку код тесно связан с кодом, который ее выявляет, рекомендуется вместо этого использовать коды ошибок. Дополнительные сведения о том, когда не следует использовать исключения, см. в разделе When Not to Use Exceptions.
Для каждой функции, которая может создавать и распространять исключение, необходимо предоставить одну из трех гарантий исключения: строгую гарантию, базовую гарантию или гарантию nothrow (noexcept). Для получения дополнительной информации см. Практическое руководство. Разработка с учетом безопасности исключений.
Создает исключения по значению, перехватывает их по ссылке. Не перехватывайте исключения, которые невозможно обработать. Для получения дополнительной информации см. Рекомендации по создание и обработок исключения (C++).
Не используйте спецификации исключений — в C ++11 они являются нерекомендуемыми. Для получения дополнительных сведений см. раздел Спецификации исключений и noexcept.
Стандартные библиотеки типов исключений, когда они применяются. Наследование пользовательских типов исключений от иерархии класса exception. Для получения дополнительной информации см. Практическое руководство. Использование объектов исключения стандартной библиотеки.
Не разрешайте исключениям уклоняться от обработки деструкторами или функциями освобождения памяти.
Исключения и производительность
Механизм исключения имеет очень минимальное снижение производительности, если исключение не создается. Если исключение создается, стоимость обхода и освобождения стека соответствует в общем и целом затратам вызова функции. Для отслеживания стека вызова после входа в блок try необходимы дополнительные структуры данных, а для раскручивания стека в случае возникновения исключения необходимы дополнительные инструкции. Однако в большинстве случаев затраты на производительность и объем памяти незначительны. Отрицательное влияние на производительность исключений, вероятно, будет значительным только при очень ограниченных в памяти системах, или в циклах, где производительность критична и где, скорее всего, будут часто возникать ошибки, а код для устранения этих ошибок тесно связан с кодом, создающим отчет. В любом случае невозможно знать фактические затраты исключений без профилирование и измерения. Даже в тех редких случаях, когда затраты существенны, они уравновешиваются повышением корректности, упрощением сопровождения и другими преимуществами, обеспечиваемыми продуманной политикой исключений.
Исключения и проверочные утверждения
Исключения и проверочные утверждения — это два разных механизма для обнаружения ошибок времени выполнения в программе. Использование утверждений для выполнения для условий во время разработки, никогда не должна быть наоборот, если весь код. Нет смысла в обработке такой ошибки с помощью исключения, поскольку ошибка указывает на то, что любой текст в коде должно быть зафиксировано, а не представляет условие, которое программе следует выйти из во время выполнения. Утверждение останавливает выполнение на операторе, чтобы можно было проверить состояние программы в отладчике; исключение продолжает выполнение из первого соответствующий обработчика catch. Используйте исключения для проверки условия ошибок, которые могут возникнуть во время выполнения, даже если код работает, например "файл не найден" или "нехватка памяти". Может потребоваться выйти из этих условий, даже если восстановление только выводит сообщение в журнал и завершает выполнение программы. Всегда проверяйте аргументы открытых функций с помощью исключений. Даже если в функции нет ошибок, вы можете не иметь полного контроля над аргументами, которые ей может передать пользователь.
Сравнение исключений C++ с исключениями Windows SEH
Программы на C, и на C++ могут использовать механизм структурированной обработки исключений (SEH) в операционной системе Windows. Понятия в SEH напоминают эти исключения в C++, за исключением того, что SEH использует __try, __except и __finally вместо конструкции try и catch. В Visual C++ исключения C++ реализованы для SEH. Однако при написании кода C++, используйте синтаксис исключения C++.
Дополнительные сведения о SEH см. в разделе Структурированная обработка исключений (C/C++).
Спецификации исключений и noexcept
Спецификации исключений были введены в C++ как способ определения исключений, которые может создавать функция. Однако спецификации исключений оказались проблематичными на практике и в стандарте черновой версии С++11 были упразднены. Рекомендуется не использовать спецификации исключений, за исключением throw(), который указывает, что исключение не позволяет исключений в escape-последовательности. Если необходимо использовать спецификации исключений типа throw(type), имейте в виду, что Visual C++ в некотором смысле отличается от стандартов. Для получения дополнительной информации см. Спецификации исключений. Спецификатор noexcept вставляется в C ++11 как основная альтернатива throw().
См. также
Основные понятия
Практическое руководство. Интерфейс между кодом с исключениями и без исключений