В современном C++ в большинстве случаев предпочтительный способ уведомления и обработки логических ошибок и ошибок среды выполнения — использование исключений. Это особенно заметно, когда стек может содержать несколько вызовов функции между функцией, которая обнаруживает ошибку и функцией, которая содержит контекст для получения сведений о его обработки. Исключения предоставляют формальный, четко определенный способ для передачи обнаружившим ошибку кодом сведений вверх по стеку вызовов.
Ошибки программы обычно делятся на две категории: логические ошибки, вызванные ошибками программирования (ошибка "индекс вне диапазона"), а также ошибки среды выполнения, которые не может контролировать программист, например ошибка "сетевая служба недоступна". В стиле программирования C и в модели COM отчеты об ошибках управляются возвратом значения, представляющего код ошибки или код статуса для определенной функции, или заданием глобальной переменной, которая может дополнительно извлекаться вызывающим объектом после каждого вызова функций, чтобы посмотреть, был ли составлен отчет об ошибках. Например, при программировании с использованием модели COM возвращаемое значение HRESULT используется для сообщения об ошибках вызывающему объекту, а в API Win32 есть функция GetLastError для извлечения последней ошибки, о которой сообщил стек вызовов. В обоих этих случаях вызывающий объект решает, признать ли код и нужно ли ответить на него соответственно. Если вызывающий код явно не обрабатывает ошибки, программа может выполнить сбой без предупреждения, либо продолжить выполнение с некорректными данными и привести к неверным результатам.
Исключения являются предпочтительными в современном языке C++ по следующим причинам:
-
Исключение вынуждает вызывающий код признать состояние ошибки и обработать его. Необработанные исключения останавливают выполнение программы.
-
Исключение перескакивает в точку в стеке вызовов, которая способна обработать ошибку. Промежуточные функции могут разрешить распространение исключения. Они не должны в соответствии с другими уровнями.
-
Механизм освобождения стека исключения уничтожает все объекты в области в соответствии с правилами чётким после создания исключения.
-
Исключение обеспечивает четкое разделение между кодом, который обнаруживает ошибку, и кодом, который обрабатывает ошибку.
Если требуется написать более переносимый код, не рекомендуется использовать структурированную обработку исключений в программе на языке C++. Однако иногда может потребоваться компиляция с /EHa и комбинирование структурированных исключений и исходного кода C++, для чего потребуются некоторые средства для обработки обоих типов исключений.Поскольку обработчик структурированных исключений не различает объекты или типизированные исключения, он не может обработать исключения, созданные кодом C++. Однако обработчики catch в C++ могут обрабатывать структурированные исключения. Потому синтаксис обработки исключений C++ (try, throw, catch) не принимается в компиляторе С, но синтаксис структурированной обработки исключений (__try, __except, __finally) поддерживается в компиляторе С++.
Дополнительные сведения об обработке структурированных исключений как исключений С++ см. в разделе _set_se_translator.
При комбинировании структурированных исключений и исключений C++ обратите внимание на следующее.
-
Исключения С++ и структурированные исключения невозможно комбинировать в одной функции.
-
Обработчики завершения (блоки __finally) выполняются всегда, даже во время очитки после создания исключения.
-
Во время обработки исключений C++ может перехватываться и сохраняться семантика очистки со всех модулях, скомпилированных с помощью параметра компилятора /EH (этот параметр разрешает семантику очистки).
-
Могут возникнуть ситуации, когда функции деструктора не вызываются для всех объектов. Например, если структурированное исключение создается при попытке вызова функции с помощью неинициализированного указателя на функцию и эта функция принимает в качестве параметров объекты, созданные перед вызовом, эти объекты не будут иметь деструкторы, вызванные во время очистки стека.
В следующем примере показан необходимый упрощенный синтаксис для вызова и перехват исключений в языке 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), который использует интеллектуальные указатели, предоставляет необходимую функциональность для очистки ресурсов.
Механизм исключения имеет очень минимальное снижение производительности, если исключение не создается. Если исключение создается, стоимость обхода и освобождения стека соответствует в общем и целом затратам вызова функции. Для отслеживания стека вызова после входа в блок try необходимы дополнительные структуры данных, а для раскручивания стека в случае возникновения исключения необходимы дополнительные инструкции. Однако в большинстве случаев затраты на производительность и объем памяти незначительны. Отрицательное влияние на производительность исключений, вероятно, будет значительным только при очень ограниченных в памяти системах, или в циклах, где производительность критична и где, скорее всего, будут часто возникать ошибки, а код для устранения этих ошибок тесно связан с кодом, создающим отчет. В любом случае невозможно знать фактические затраты исключений без профилирование и измерения. Даже в тех редких случаях, когда стоимость является существенной, вы можете ощутить выгоду от повышения ясности, упрощения сопровождения и других преимуществ, предоставляемых хорошо спроектированной политикой исключений.
Исключения и проверочные утверждения — это два разных механизма для обнаружения ошибок времени выполнения в программе. Использование утверждений для выполнения для условий во время разработки, никогда не должна быть наоборот, если весь код. Нет смысла в обработке такой ошибки с помощью исключения, поскольку ошибка указывает на то, что любой текст в коде должно быть зафиксировано, а не представляет условие, которое программе следует выйти из во время выполнения. Утверждение останавливает выполнение на операторе, чтобы можно было проверить состояние программы в отладчике; исключение продолжает выполнение из первого соответствующий обработчика catch. Используйте исключения для проверки условия ошибок, которые могут возникнуть во время выполнения, даже если код работает, например "файл не найден" или "нехватка памяти". Может потребоваться выйти из этих условий, даже если восстановление только выводит сообщение в журнал и завершает выполнение программы. Всегда проверяйте аргументы открытых функций с помощью исключений. Даже если в функции нет ошибок, вы можете не иметь полного контроля над аргументами, которые ей может передать пользователь.