Исключение runtime error c

Типы исключений в языке програмирования C++, обработка нескольких видов исключений, stdexcept

Типы исключений

Последнее обновление: 05.10.2017

Кроме типа exception в C++ есть еще несколько производных типов исключений, которые могут использоваться при различных ситуациях. Основные из них:

  • runtime_error: общий тип исключений, которые возникают во время выполнения

  • range_error: исключение, которое возникает, когда полученный результат превосходит допустимый диапазон

  • overflow_error: исключение, которое возникает, если полученный результат превышает допустимый диапазон

  • underflow_error: исключение, которое возникает, если полученный в вычислениях результат имеет недопустимые отрицательное значение (выход за нижнюю допустимую границу значений)

  • logic_error: исключение, которое возникает при наличии логических ошбок к коде программы

  • domain_error: исключение, которое возникает, если для некоторого значения, передаваемого в функцию, не определено результата

  • invalid_argument: исключение, которое возникает при передаче в функцию некорректного аргумента

  • length_error: исключение, которое возникает при попытке создать объект большего размера, чем допустим для данного типа

  • out_of_range: исключение, которое возникает при попытке доступа к элементам вне допустимого диапазона

Типы исключений в C++ и std::exception

Большинство этих типов определено в заголовочном файле stdexcept, за исключением класса bad_alloc,
который определн в файле new, и класса bad_cast, который определен в файле
type_info.

В отличие от классов exception, bad_alloc и bad_cast в конструкторы других типов можно передать строку, то есть таким образом можно передать сообщение об ошибке.

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

При использовании нескольких блоков catch вначале помещаются блоки catch, которые обрабатывают более частные исключения, а только потом блоки catch с более
общими типами исключений:

#include <iostream>
#include <exception>
#include <stdexcept>

double divide(int, int);

int main()
{
	int x = 500;
	int y = 0;
	try
	{
		double z = divide(x, y);
		std::cout << z << std::endl;
	}
	catch (std::overflow_error err)
	{
		std::cout << "Overflow_error: " << err.what() << std::endl;
	}
	catch (std::runtime_error err)
	{
		std::cout << "Runtime_error: " << err.what() << std::endl;
	}
	catch (std::exception err)
	{
		std::cout << "Exception!!!"<< std::endl;
	}
	std::cout << "The End..." << std::endl;
	return 0;
}

double divide(int a, int b)
{
	if (b == 0)
		throw std::runtime_error("Division by zero!");
	return a / b;
}

Здесь функция divide, если параметр b равен 0, выбрасывает исключение типа runtime_error. Исключение инициализируется
сообщением об ошибке «Division by zero!».

В функции main конструкция try..catch использует три блока catch. Причем последний блок представляет самый общий тип исключений exception. Второй блок
обрабатывает исключения типа runtime_error, производный от exception. А первый блок обрабатывает исключения типа overflow_error, который является производным от
runtime_error.

Также все типы исключений имеют метод what(), который возвращает информацию об ошибке. И в данном случае программа выдаст следующий результат:

Runtime_error: Division by zero!
The End...

Defines a type of object to be thrown as exception. It reports errors that are due to events beyond the scope of the program and can not be easily predicted.

Exceptions of type std::runtime_error are thrown by the following standard library components: std::locale::locale and std::locale::combine.

In addition, the following standard exception types are derived from std::runtime_error:

  • std::range_error
  • std::overflow_error
  • std::underflow_error
  • std::chrono::ambiguous_local_time
  • std::chrono::nonexistent_local_time
  • std::format_error
(since C++20)

std-runtime error-inheritance.svg

Inheritance diagram

Contents

  • 1 Member functions
  • 2 std::runtime_error::runtime_error
    • 2.1 Parameters
    • 2.2 Exceptions
    • 2.3 Notes
  • 3 std::runtime_error::operator=
    • 3.1 Parameters
    • 3.2 Return value
    • 3.3 Notes
  • 4 Inherited from std::exception
    • 4.1 Member functions
    • 4.2 Defect reports

[edit] Member functions

constructs a new runtime_error object with the given message
(public member function)
replaces the runtime_error object
(public member function)

std::runtime_error::runtime_error

runtime_error( const std::string& what_arg );

(1)

runtime_error( const char* what_arg );

(2)
(3)

runtime_error( const runtime_error& other );

(until C++11)

runtime_error( const runtime_error& other ) noexcept;

(since C++11)

1) Constructs the exception object with what_arg as explanatory string. After construction, std::strcmp(what(), what_arg.c_str()) == 0.

2) Constructs the exception object with what_arg as explanatory string. After construction, std::strcmp(what(), what_arg) == 0.

3) Copy constructor. If *this and other both have dynamic type std::runtime_error then std::strcmp(what(), other.what()) == 0. No exception can be thrown from the copy constructor. (until C++11)

Parameters

what_arg explanatory string
other another exception object to copy

Exceptions

Notes

Because copying std::runtime_error is not permitted to throw exceptions, this message is typically stored internally as a separately-allocated reference-counted string. This is also why there is no constructor taking std::string&&: it would have to copy the content anyway.

Before the resolution of LWG issue 254, the non-copy constructor can only accept std::string. It makes dynamic allocation mandatory in order to construct a std::string object.

After the resolution of LWG issue 471, a derived standard exception class must have a publicly accessible copy constructor. It can be implicitly defined as long as the explanatory strings obtained by what() are the same for the original object and the copied object.

std::runtime_error::operator=

runtime_error& operator=( const runtime_error& other );

(until C++11)

runtime_error& operator=( const runtime_error& other ) noexcept;

(since C++11)

Assigns the contents with those of other. If *this and other both have dynamic type std::runtime_error then std::strcmp(what(), other.what()) == 0 after assignment. No exception can be thrown from the copy assignment operator. (until C++11)

Parameters

other another exception object to assign with

Return value

*this

Notes

After the resolution of LWG issue 471, a derived standard exception class must have a publicly accessible copy assignment operator. It can be implicitly defined as long as the explanatory strings obtained by what() are the same for the original object and the copied object.

Inherited from std::exception

Member functions

destroys the exception object
(virtual public member function of std::exception) [edit]
returns an explanatory string
(virtual public member function of std::exception) [edit]

[edit] Defect reports

The following behavior-changing defect reports were applied retroactively to previously published C++ standards.

DR Applied to Behavior as published Correct behavior
LWG 254 C++98 the constructor accepting const char* was missing added
LWG 471 C++98 the explanatory strings of std::runtime_error‘s
copies were implementation-defined
they are the same as that of the
original std::runtime_error object

Содержание

  1. Исключение runtime error c
  2. Обработка и создание исключений в .NET
  3. Исключения
  4. Исключения и традиционные методы обработки ошибок
  5. Часто встречающиеся исключения
  6. Use exceptions
  7. Creating and Throwing Exceptions
  8. Things to Avoid When Throwing Exceptions
  9. Defining Exception Classes
  10. C# Language Specification
  11. C++ — Урок 011. Исключения
  12. Инварианты
  13. Виды исключений
  14. std::logic_error
  15. std::invalid_argument
  16. std::domain_error
  17. std::length_error
  18. std::out_of_range
  19. std::future_error
  20. std::runtime_error
  21. std::range_error
  22. std::overflow_error
  23. std::underflow_error
  24. std::system_error
  25. std::ios_base::failure
  26. std::bad_typeid
  27. std::bad_cast
  28. std::bad_weak_ptr
  29. std::bad_function_call
  30. std::bad_alloc
  31. std::bad_array_new_length
  32. std::bad_exception

Исключение runtime error c

Кроме типа exception в C++ есть еще несколько производных типов исключений, которые могут использоваться при различных ситуациях. Основные из них:

runtime_error : общий тип исключений, которые возникают во время выполнения

range_error : исключение, которое возникает, когда полученный результат превосходит допустимый диапазон

overflow_error : исключение, которое возникает, если полученный результат превышает допустимый диапазон

underflow_error : исключение, которое возникает, если полученный в вычислениях результат имеет недопустимые отрицательное значение (выход за нижнюю допустимую границу значений)

logic_error : исключение, которое возникает при наличии логических ошбок к коде программы

domain_error : исключение, которое возникает, если для некоторого значения, передаваемого в функцию, не определено результата

invalid_argument : исключение, которое возникает при передаче в функцию некорректного аргумента

length_error : исключение, которое возникает при попытке создать объект большего размера, чем допустим для данного типа

out_of_range : исключение, которое возникает при попытке доступа к элементам вне допустимого диапазона

Большинство этих типов определено в заголовочном файле stdexcept , за исключением класса bad_alloc , который определн в файле new , и класса bad_cast , который определен в файле type_info .

В отличие от классов exception, bad_alloc и bad_cast в конструкторы других типов можно передать строку, то есть таким образом можно передать сообщение об ошибке.

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

При использовании нескольких блоков catch вначале помещаются блоки catch, которые обрабатывают более частные исключения, а только потом блоки catch с более общими типами исключений:

Здесь функция divide, если параметр b равен 0, выбрасывает исключение типа runtime_error . Исключение инициализируется сообщением об ошибке «Division by zero!».

В функции main конструкция try..catch использует три блока catch. Причем последний блок представляет самый общий тип исключений exception. Второй блок обрабатывает исключения типа runtime_error, производный от exception. А первый блок обрабатывает исключения типа overflow_error, который является производным от runtime_error.

Также все типы исключений имеют метод what() , который возвращает информацию об ошибке. И в данном случае программа выдаст следующий результат:

Источник

Обработка и создание исключений в .NET

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

Исключения

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

В .NET исключение — это объект, наследуемый от класса System.Exception. Исключение создается из области кода, где произошла проблема. Исключение передается вверх по стеку до тех пор, пока его не обработает приложение либо программа не завершится.

Исключения и традиционные методы обработки ошибок

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

Создание и обработка исключений работают одинаково для языков программирования .NET.

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

Исключения можно создавать между разными процессами и даже компьютерами.

В приложение можно добавить код обработки исключений для повышения надежности программы.

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

Часто встречающиеся исключения

В следующей таблице перечислены некоторые общие исключения с примерами возможных причин.

Источник

Use exceptions

In C#, errors in the program at run time are propagated through the program by using a mechanism called exceptions. Exceptions are thrown by code that encounters an error and caught by code that can correct the error. Exceptions can be thrown by the .NET runtime or by code in a program. Once an exception is thrown, it propagates up the call stack until a catch statement for the exception is found. Uncaught exceptions are handled by a generic exception handler provided by the system that displays a dialog box.

Exceptions are represented by classes derived from Exception. This class identifies the type of exception and contains properties that have details about the exception. Throwing an exception involves creating an instance of an exception-derived class, optionally configuring properties of the exception, and then throwing the object by using the throw keyword. For example:

After an exception is thrown, the runtime checks the current statement to see whether it is within a try block. If it is, any catch blocks associated with the try block are checked to see whether they can catch the exception. Catch blocks typically specify exception types; if the type of the catch block is the same type as the exception, or a base class of the exception, the catch block can handle the method. For example:

If the statement that throws an exception isn’t within a try block or if the try block that encloses it has no matching catch block, the runtime checks the calling method for a try statement and catch blocks. The runtime continues up the calling stack, searching for a compatible catch block. After the catch block is found and executed, control is passed to the next statement after that catch block.

A try statement can contain more than one catch block. The first catch statement that can handle the exception is executed; any following catch statements, even if they’re compatible, are ignored. Order catch blocks from most specific (or most-derived) to least specific. For example:

Before the catch block is executed, the runtime checks for finally blocks. Finally blocks enable the programmer to clean up any ambiguous state that could be left over from an aborted try block, or to release any external resources (such as graphics handles, database connections, or file streams) without waiting for the garbage collector in the runtime to finalize the objects. For example:

If WriteByte() threw an exception, the code in the second try block that tries to reopen the file would fail if file.Close() isn’t called, and the file would remain locked. Because finally blocks are executed even if an exception is thrown, the finally block in the previous example allows for the file to be closed correctly and helps avoid an error.

If no compatible catch block is found on the call stack after an exception is thrown, one of three things occurs:

  • If the exception is within a finalizer, the finalizer is aborted and the base finalizer, if any, is called.
  • If the call stack contains a static constructor, or a static field initializer, a TypeInitializationException is thrown, with the original exception assigned to the InnerException property of the new exception.
  • If the start of the thread is reached, the thread is terminated.

Источник

Creating and Throwing Exceptions

Exceptions are used to indicate that an error has occurred while running the program. Exception objects that describe an error are created and then thrown with the throw keyword. The runtime then searches for the most compatible exception handler.

Programmers should throw exceptions when one or more of the following conditions are true:

The method can’t complete its defined functionality. For example, if a parameter to a method has an invalid value:

An inappropriate call to an object is made, based on the object state. One example might be trying to write to a read-only file. In cases where an object state doesn’t allow an operation, throw an instance of InvalidOperationException or an object based on a derivation of this class. The following code is an example of a method that throws an InvalidOperationException object:

When an argument to a method causes an exception. In this case, the original exception should be caught and an ArgumentException instance should be created. The original exception should be passed to the constructor of the ArgumentException as the InnerException parameter:

The example above is for illustrative purposes. Index validating via exceptions is in most cases bad practice. Exceptions should be reserved to guard against exceptional program conditions, not for argument checking as above.

Exceptions contain a property named StackTrace. This string contains the name of the methods on the current call stack, together with the file name and line number where the exception was thrown for each method. A StackTrace object is created automatically by the common language runtime (CLR) from the point of the throw statement, so that exceptions must be thrown from the point where the stack trace should begin.

All exceptions contain a property named Message. This string should be set to explain the reason for the exception. Information that is sensitive to security shouldn’t be put in the message text. In addition to Message, ArgumentException contains a property named ParamName that should be set to the name of the argument that caused the exception to be thrown. In a property setter, ParamName should be set to value .

Public and protected methods throw exceptions whenever they can’t complete their intended functions. The exception class thrown is the most specific exception available that fits the error conditions. These exceptions should be documented as part of the class functionality, and derived classes or updates to the original class should retain the same behavior for backward compatibility.

Things to Avoid When Throwing Exceptions

The following list identifies practices to avoid when throwing exceptions:

  • Don’t use exceptions to change the flow of a program as part of ordinary execution. Use exceptions to report and handle error conditions.
  • Exceptions shouldn’t be returned as a return value or parameter instead of being thrown.
  • Don’t throw System.Exception, System.SystemException, System.NullReferenceException, or System.IndexOutOfRangeException intentionally from your own source code.
  • Don’t create exceptions that can be thrown in debug mode but not release mode. To identify run-time errors during the development phase, use Debug Assert instead.

Defining Exception Classes

Programs can throw a predefined exception class in the System namespace (except where previously noted), or create their own exception classes by deriving from Exception. The derived classes should define at least three constructors: one parameterless constructor, one that sets the message property, and one that sets both the Message and InnerException properties. For example:

Add new properties to the exception class when the data they provide is useful to resolving the exception. If new properties are added to the derived exception class, ToString() should be overridden to return the added information.

C# Language Specification

For more information, see Exceptions and The throw statement in the C# Language Specification. The language specification is the definitive source for C# syntax and usage.

Источник

C++ — Урок 011. Исключения

Что такое исключение? Это ситуация, которая не предусмотрена стандартным поведением программы. Например, попытка доступа к элементу в классе Vector (который мы разбирали в статье про классы ), который не существует. То есть происходит выход за пределы вектора. В данном случае можно воспользоваться исключениями, чтобы прервать выполнение программы. Это необходимо потому, что

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

Для разрешения таких ситуация в C++ можно использовать технику исключений.

Рассмотрим, как написать вызов исключения в случае попытки доступа к элементу по индексу, который не существует в классе Vector.

Здесь применяется исключение out_of_range. Данное исключение определено в заголовочном файле .

Оператор throw передаёт контроль обработчику для исключений типа out_of_range в некоторой функции, которая прямо или косвенно вызывает Vector::operator . Для того, чтобы обработать исключения необходимо воспользоваться блоком операторов try catch.

Инварианты

Также блоки try catch позволяют производить обработку нескольких различных исключений, что вносит инвариантность в работу механизма исключений C++.

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

Данный конструктор может выбросить исключение в двух случаях:

  • Если в качестве аргумента size будет передано отрицательное значение
  • Если оператор new не сможет выделить память

length_error — это стандартный оператор исключений, поскольку библиотека std часто использует данные исключения при своей работе.

Обработка исключений будет выглядеть следующим образом:

Также можно выделить свои собственные исключения.

Виды исключений

Все исключения стандартной библиотеки наследуются от std::exception.

На данный момент существуют следующие виды исключений:

  • logic_error
  • invalid_argument
  • domain_error
  • length_error
  • out_of_range
  • future_error (C++11)
  • runtime_error
  • range_error
  • overflow_error
  • underflow_error
  • system_error (C++11)
  • ios_base::failure (начиная с C++11)
  • bad_typeid
  • bad_cast
  • bad_weak_ptr (C++11)
  • bad_function_call (C++11)
  • bad_alloc
  • bad_array_new_length (C++11)
  • bad_exception
  • ios_base::failure (до C++11)

std::logic_error

Исключение определено в заголовочном файле

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

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

std::invalid_argument

Исключение определено в заголовочном файле

Наследован от std::logic_error. Определяет исключение, которое должно быть брошено в случае неправильного аргумента.

Например, на MSDN приведён пример, когда в объект класса bitset из стандартной библиотеки

В данном примере передаётся неправильная строка, внутри которой имеется символ ‘b’, который будет ошибочным.

std::domain_error

Исключение определено в заголовочном файле

Наследован от std::logic_error. Определяет исключение, которое должно быть брошено в случае если математическая функция не определена для того аргумента, который ей передаётся, например:

std::length_error

Исключение определено в заголовочном файле

Наследован от std::logic_error. Определяет исключение, которое должно быть броше в том случае, когда осуществляется попытка реализации превышения допустим пределов для объекта. Как это было показано для размера вектора в начале статьи.

std::out_of_range

Исключение определено в заголовочном файле

Наследован от std::logic_error. Определяет исключение, которое должно быть брошено в том случае, когда происходит выход за пределы допустимого диапазона значений объекта. Как это было показано для диапазона значений ветора в начале статьи.

std::future_error

Исключение определено в заголовочном файле

Наследован от std::logic_error. Данное исключение может быть выброшено в том случае, если не удалось выполнить функцию, которая работает в асинхронном режиме и зависит от библиотеки потоков. Это исключение несет код ошибки совместимый с std::error_code .

std::runtime_error

Исключение определено в заголовочном файле

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

std::range_error

Исключение определено в заголовочном файле

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

std::overflow_error

Исключение определено в заголовочном файле

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

std::underflow_error

Исключение определено в заголовочном файле

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

std::system_error

Исключение определено в заголовочном файле

std::system_error — это тип исключения, которое вызывается различными функциями стандартной библиотеки (как правило, функции, которые взаимодействуют с операционной системой, например, конструктор std::thread ), при этом исключение имеет соответствующий std::error_code .

std::ios_base::failure

Исключение определено в заголовочном файле

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

std::bad_typeid

Исключение определено в заголовочном файле

Исключение этого типа возникает, когда оператор typeid применяется к нулевому указателю полиморфного типа.

std::bad_cast

Исключение определено в заголовочном файле

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

std::bad_weak_ptr

Исключение определено в заголовочном файле

std::bad_weak_ptr – тип объекта, генерируемый в качестве исключения конструкторами std::shared_ptr , которые принимают std::weak_ptr в качестве аргумента, когда std::weak_ptr ссылается на уже удаленный объект.

std::bad_function_call

Исключение определено в заголовочном файле

Данное исключение генерируется в том случае, если был вызван метод std::function::operator() объекта std::function , который не получил объекта функции, то есть ему был передан в качестве инициализатора nullptr, например, а объект функции так и не был передан.

std::bad_alloc

Исключение определено в заголовочном файле

Вызывается в том случае, когда не удаётся выделить память.

std::bad_array_new_length

Исключение определено в заголовочном файле

Исключение вызывается в следующих случаях:

  1. Массив имеет отрицательный размер
  2. Общий размер нового массива превысил максимальное значение, определяемое реализацией
  3. Количество элементов инициализации превышает предлагаемое количество инициализирующих элементов

std::bad_exception

Исключение определено в заголовочном файле

std::bad_exception — это тип исключения в C++, которое выполняется в следующих ситуациях:

  1. Если нарушается динамическая спецификация исключений
  2. Если std::exception_ptr хранит копию пойманного исключения, и если конструктор копирования объекта исключения поймал current_exception, тогда генерируется исключение захваченных исключений.

Рекомендуем хостинг TIMEWEB

Рекомендуемые статьи по этой тематике

Источник

Поговорим об исключениях в C++, начиная определением и заканчивая грамотной обработкой.

  1. Инструмент программирования для исключительных ситуаций
  2. Исключения: панацея или нет
  3. Синтаксис исключений в C++
  4. Базовые исключения стандартной библиотеки
  5. Заключение

Георгий Осипов

Георгий Осипов


Один из авторов курса «Разработчик C++» в Яндекс Практикуме, разработчик в Лаборатории компьютерной графики и мультимедиа ВМК МГУ

Исключения — важный инструмент в современном программировании. В большинстве источников тема исключений раскрывается не полностью: не описана механика их работы, производительность или особенности языка C++.

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

Статья поделена на две части. Первая перед вами и содержит базовые, но важные сведения. Вторая выйдет чуть позже. В ней — информация для более продвинутых разработчиков.

В первой части разберёмся:

  • для чего нужны исключения;
  • особенности C++;
  • синтаксис выбрасывания и обработки исключений;
  • особые случаи, связанные с исключениями.

Также рассмотрим основные стандартные типы исключений, где и для чего они применяются.

Мы опираемся на современные компиляторы и Стандарт C++20. Немного затронем C++23 и даже C++03.

Если вы только осваиваете C++, возможно, вам будет интересен курс «Разработчик C++» в Яндекс Практикуме. У курса есть бесплатная вводная часть. Именно она может стать вашим первым шагом в мир C++. Для тех, кто знаком с программированием, есть внушительная ознакомительная часть, тоже бесплатная.

Инструмент программирования для исключительных ситуаций

В жизни любой программы бывают моменты, когда всё идёт не совсем так, как задумывал разработчик. Например:

  • в системе закончилась оперативная память;
  • соединение с сервером внезапно прервалось;
  • пользователь выдернул флешку во время чтения или записи файла;
  • понадобилось получить первый элемент списка, который оказался пустым;
  • формат файла не такой, как ожидалось.

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

Хороший программист старается предусмотреть подобные ситуации. Однако это бывает сложно: перечисленные проблемы обладают неприятным свойством — они могут возникнуть практически в любой момент.

На помощь программисту приходят исключения (exception). Так называют объекты, которые хранят данные о возникшей проблеме. Механизмы исключений в разных языках программирования очень похожи. В зависимости от терминологии языка исключения либо выбрасывают (throw), либо генерируют (raise). Это происходит в тот момент, когда программа не может продолжать выполнять запрошенную операцию.

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

Если обработчик всё же найден, то он ловит (catch) исключение и программа продолжает работать как обычно. В некоторых языках вместо catch используется глагол except (исключить).

Обработчик ловит не все исключения, а только некоторые — те, что возникли в конкретной части определённой функции. Эту часть нужно явно обозначить, для чего используют конструкцию try (попробовать). Также обработчик не поймает исключение, которое ранее попало в другой обработчик. После обработки исключения программа продолжает выполнение как ни в чём не бывало.

Исключения: панацея или нет

Перед тем как совершить операцию, нужно убедиться, что она корректна. Если да — совершить эту операцию, а если нет — выбросить исключение. Так делается в некоторых языках, но не в C++. Проверка корректности — это время, а время, как известно, деньги. В C++ считается, что программист знает, что делает, и не нуждается в дополнительных проверках. Это одна из причин, почему программы на C++ такие быстрые.

Но за всё нужно платить. Если вы не уследили и сделали недопустимую операцию, то в менее производительных языках вы получите исключение, а в C++ — неопределённое поведение. Исключение можно обработать и продолжить выполнение программы. Неопределённое поведение гарантированно обработать нельзя.

Но некоторые виды неопределённого поведения вполне понятны и даже могут быть обработаны. Это зависит от операционной системы:

  • сигналы POSIX — низкоуровневые уведомления, которые отправляются программе при совершении некорректных операций и в некоторых других случаях;
  • структурированные исключения Windows (SEH) — специальные исключения, которые нельзя обработать средствами языка.

Особенность C++ в том, что не любая ошибка влечёт исключение, и не любую ошибку можно обработать. Но если для операции производительность не так критична, почему бы не сделать проверку?

У ряда операций в C++ есть две реализации. Одна супербыстрая, но вы будете отвечать за корректность, а вторая делает проверку и выбрасывает исключение в случае ошибки. Например, к элементу класса std::vector можно обратиться двумя способами:

  • vec[15] — ничего не проверяет. Если в векторе нет элемента с индексом 15, вы получаете неопределённое поведение. Это может быть сигнал SIGSEGV, некорректное значение или взрыв компьютера.
  • vec.at(15) — то же самое, но в случае ошибки выбрасывается исключение, которое можно обработать.

В C++ вам даётся выбор: делать быстро или делать безопасно. Часто безопасность важнее, но в определённых местах программы любое промедление критично.

Ловим исключения

Начнём с примера:

void SomeFunction() {
    DoSomething0();

    try {
        SomeClass var;

        DoSomething1();
        DoSomething2();

        // ещё код

        cout << "Если возникло исключение, то этот текст не будет напечатан" << std::endl;
    }
    catch(ExceptionType e) {
        std::cout << "Поймано исключение: " << e.what() << std::endl;
        // ещё код
    }

    std::cout << "Это сообщение не будет выведено, если возникло исключение в DoSomething0 или "
                  "непойманное исключение внутри блока try." << std::endl;
}

В примере есть один try-блок и один catch-блок. Если в блоке try возникает исключение типа ExceptionType, то выполнение блока заканчивается. При этом корректно удаляются созданные объекты — в данном случае переменная var. Затем управление переходит в конструкцию catch. Сам объект исключения передаётся в переменную e. Выводя e.what(), мы предполагаем, что у типа ExceptionType есть метод what.

Если в блоке try возникло исключение другого типа, то управление также прервётся, но поиск обработчика будет выполняться за пределами функции SomeFunction — выше по стеку вызовов. Это также касается любых исключений, возникших вне try-блока.

Во всех случаях объект var будет корректно удалён.

Исключение не обязано возникнуть непосредственно внутри DoSomething*(). Будут обработаны исключения, возникшие в функциях, вызванных из DoSomething*, или в функциях, вызванных из тех функций, да и вообще на любом уровне вложенности. Главное, чтобы исключение не было обработано ранее.

Ловим исключения нескольких типов

Можно указать несколько блоков catch, чтобы обработать исключения разных типов:

void SomeFunction() {
    DoSomething0();

    try {
        DoSomething1();
        DoSomething2();
        // ещё код
    }
    catch(ExceptionType1 e) {
        std::cout << "Some exception of type ExceptionType1: " << e.what() << std::endl;
        // ещё код
    }
    catch(ExceptionType2 e) {
        std::cout << "Some exception of type ExceptionType2: " << e.what() << std::endl;
        // ещё код
    }
    // ещё код
}

Ловим все исключения

void SomeFunction() {
    DoSomething0();

    try {
        DoSomething1();
        DoSomething2();
        // ещё код
    }
    catch(...) {
        std::cout << "An exception any type" << std::endl;
        // ещё код
    }
    // ещё код
}

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

Перебрасываем исключение

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

void SomeFunction() {
    DoSomething0();

    try {
        DoSomething1();
        DoSomething2();
        // ещё код
    }
    catch(...) {
        std::cout << "Какое-то исключение неизвестного типа. Сейчас не можем его обработать" << std::endl;
        throw; // перебрасываем исключение
    }
    // ещё код
}

Можно использовать throw в catch-блоках с указанным типом исключения. Но если поместить throw вне блока catch, то программа тут же аварийно завершит работу через вызов std::terminate().

Перебросить исключение можно другим способом:

std::rethrow_exception(std::current_exception())

Этот способ обладает дополнительным преимуществом: можно сохранить исключение и перебросить его в другом месте. Однако результат std::current_exception() — это не объект исключения, поэтому его можно использовать только со специализированными функциями.

Принимаем исключение по ссылке

Чтобы избежать лишних копирований, можно ловить исключение по ссылке или константной ссылке:

void SomeFunction() {
    DoSomething0();

    try {
        DoSomething1();
        DoSomething2();
        // ещё код
    }
    catch(ExceptionType& e) {
        std::cout << "Some exception of type ExceptionType: " << e.what() << std::endl;
        // ещё код
    }
    catch(const OtherExceptionType& e) {
        std::cout << "Some exception of type OtherExceptionType: " << e.what() << std::endl;
        // ещё код
    }
}

Это особенно полезно, когда мы ловим исключение по базовому типу.

Выбрасываем исключения

Чтобы поймать исключение, нужно его вначале выбросить. Для этого применяется throw.

Если throw используется с параметром, то он не перебрасывает исключение, а выбрасывает новое. Параметр может быть любого типа, даже примитивного. Использовать такую конструкцию разрешается в любом месте программы:

void ThrowIfNegative(int x) {
    if (x < 0) {
        // выбрасываем исключение типа int
        throw x;
    }
}

int main() {
    try {
        ThrowIfNegative(10);
        ThrowIfNegative(-15);
        ThrowIfNegative(0);
        cout << "Этот текст никогда не будет напечатан" << std::endl;
    }
    // ловим выброшенное исключение
    catch(int x) {
        cout << "Поймано исключение типа int, содержащее число " << x << std::endl;
    }
}

Вывод: «Поймано исключение типа int, содержащее число –15».

Создаём типы для исключений

Выбрасывать int или другой примитивный тип можно, но это считается дурным тоном. Куда лучше создать специальный тип, который будет использоваться только для исключений. Причём удобно для каждого вида ошибок сделать отдельный класс. Он даже не обязан содержать какие-то данные или методы: отличать исключения друг от друга можно по названию типа.

class IsZeroException{};
struct IsNegativeException{};

void ThrowIfNegative(int x) {
    if (x < 0) {
        // Выбрасывается не тип, а объект.
        // Не забываем скобки, чтобы создать объект заданного типа:
        throw IsNegativeException();
    }
}

void ThrowIfZero(int x) {
    if (x == 0) {
        throw IsZeroException();
    }
}

void ThrowIfNegativeOrZero(int x) {
    ThrowIfNegative(x);
    ThrowIfZero(x);
}

int main() {
    try {
        ThrowIfNegativeOrZero(10);
        ThrowIfNegativeOrZero(-15);
        ThrowIfNegativeOrZero(0);
    }
    catch(IsNegativeException x) {
        cout << "Найдено отрицательное число" << std::endl;
    }
    catch(IsZeroException x) {
        cout << "Найдено нулевое число" << std::endl;
    }
}

В итоге будет напечатана только фраза: «Найдено отрицательное число», поскольку –15 проверено раньше нуля.

Ловим исключение по базовому типу

Чтобы поймать исключение, тип обработчика должен в точности совпадать с типом исключения. Например, нельзя поймать исключение типа int обработчиком типа unsigned int.

Но есть ситуации, в которых типы могут не совпадать. Про одну уже сказано выше: можно ловить исключение по ссылке. Есть ещё одна возможность — ловить исключение по базовому типу.

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

class NumericException {
public:
    virtual std::string_view what() const = 0;
}

// Класс — наследник NumericException.
class IsZeroException : public NumericException {
public:
    std::string_view what() const override {
        return "Обнаружен ноль";
    }
}

// Ещё один наследник NumericException.
class IsNegativeException : public NumericException {
public:
    std::string_view what() const override {
        return "Обнаружено отрицательное число";
    }
}

void ThrowIfNegative(int x) {
    if (x < 0) {
        // Выбрасывается не тип, а объект.
        // Не забываем скобки, чтобы создать объект заданного типа:
        throw IsNegativeException();
    }
}

void ThrowIfZero(int x) {
    if (x == 0) {
        throw IsZeroException();
    }
}

void ThrowIfNegativeOrZero(int x) {
    ThrowIfNegative(x);
    ThrowIfZero(x);
}

int main() {
    try {
        ThrowIfNegativeOrZero(10);
        ThrowIfNegativeOrZero(-15);
        ThrowIfNegativeOrZero(0);
    }
    // Принимаем исключение базового типа по константной ссылке (&):
    catch(const NumericException& e) {
        std::cout << e.what() << std::endl;
    }
}

Выбрасываем исключение в тернарной операции ?:

Напомню, что тернарная операция ?: позволяет выбрать из двух альтернатив в зависимости от условия:

std::cout << (age >= 18 ? "Проходите" : "Извините, вход в бар с 18 лет") << std::endl;

Оператор throw можно использовать внутри тернарной операции в качестве одного из альтернативных значений. Например, так можно реализовать безопасное деление:

int result = y != 0 ? x / y : throw IsZeroException();

Это эквивалентно такой записи:

int result;
if (y != 0) {
    result = x / y;
} 
else {
    throw IsZeroException();
}

Согласитесь, первый вариант лаконичнее. Так можно выбрасывать несколько исключений в одном выражении:

// Вычислим корень отношения чисел:
int result = y == 0 ? throw IsZeroException() : x / y < 0 ? throw IsNegativeException() : sqrt(x / y);

Вся функция — try-блок

Блок try может быть всем телом функции:

int SomeFunction(int x) try {
    return DoSomething(x);
}
catch(ExceptionType e) {
    std::cout << "Some exception of type ExceptionType: " << e.what() << std::endl;
    // ещё код

    // Для того, кто вызвал функцию, всё прошло штатно: исключение поймано.
    // Мы должны возвратить значение:
    return –1; 
}

Тут мы просто опустили фигурные скобки функции. По-другому можно записать так:

int SomeFunction(int x) {
    try {
        return DoSomething(x);
    }
    catch(ExceptionType e) {
        std::cout << "Some exception of type ExceptionType: " << e.what() << std::endl;
        // ещё код
    
        // Для того, кто вызвал функцию, всё прошло штатно: исключение поймано.
        // Мы должны возвратить значение:
        return –1; 
    }
}

Исключения в конструкторе

Есть как минимум два случая возникновения исключений в конструкторе объекта:

  1.  Внутри тела конструктора.
  2. При конструировании данных объекта.

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

Во втором случае исключение тоже можно поймать, если использовать try-блок в качестве тела конструктора. Однако тут есть особенность: сделать вид, что ничего не было, не получится. Объект всё равно будет считаться недоконструированным:

class IsZeroException{};

// Функция выбросит исключение типа IsZeroException
// если аргумент равен нулю.
void ThrowIf0(int x) {
    if (x == 0) {
        throw IsZeroException();
    }
}

// Класс содержит только одно число.
// Он выбрасывает исключение в конструкторе, если число нулевое.
class NotNullInt {
public:
    NotNullInt(int x) : x_(x) {
        ThrowIf0(x_);
    }

private:
    int x_;
}

class Ratio {
public:
    // Инициализаторы пишем после try:
    Ratio(int x, int y) try : x_(x), y_(y) {
    }
    catch(IsZeroException e) {
        std::cout << "Знаменатель дроби не может быть нулём" << std::endl;
        // Тут неявный throw; — конструктор прерван
    }

private:
    int x_;
    NotNullInt y_;
};

int main() {
    Ratio(10, 15);
    try {
        Ratio(15, 0);
    }
    catch(...) {
        std::cout << "Дробь не построена" << std::endl;
    }
}

Тут мы увидим оба сообщения: «Знаменатель дроби не может быть нулём» и «Дробь не построена».

Если объект недоконструирован, то его деструктор не вызывается. Это логичная, но неочевидная особенность языка. Однако все полностью построенные члены – данные объекта будут корректно удалены:

#include 

class A{
public:
    A() {
        std::cout << "A constructed" << std::endl;
    }
    ~A() {
        std::cout << "A destructed" << std::endl;
    }
private:
}

class B{
public:
    B() {
        std::cout << "B constructed" << std::endl;
        throw 1;
    }
    ~B() {
        // Этой надписи мы не увидим:
        std::cout << "B destructed" << std::endl;
    }
    
private:
    A a;
};

int main() {
    try {
        B b;
    }
    catch (...) {
    }
}

Запустим код и увидим такой вывод:

A constructed
B constructed
A destructed

Объект типа A создался и удалился, а объект типа B создался не до конца и поэтому не удалился.

Не все исключения в конструкторах можно обработать. Например, нельзя поймать исключения, выброшенные при конструировании глобальных и thread_local объектов, — в этом случае будет вызван std::terminate.

Исключения в деструкторе

В этом разделе примера не будет, потому что исключения в деструкторе — нежелательная практика. Бывает, что язык удаляет объекты вынужденно, например, при поиске обработчика выброшенного исключения. Если во время этого возникнет другое исключение в деструкторе какого-то объекта, то это приведёт к вызову std::terminate.

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

Обрабатываем непойманные исключения

Поговорка «не пойман — не вор» для исключений не работает. Непойманные исключения приводят к завершению программы через std::terminate. Это нештатная ситуация, но можно предотвратить немедленное завершение, добавив обработчик для std::terminate:

int main() {
    // Запишем обработчик в переменную terminate_handler
    auto terminate_handler = []() {
        auto e_ptr = std::current_exception();
        if (e_ptr) {
            try {
                // Перебросим исключение:
                std::rethrow_exception(e_ptr);
            } catch (const SomeType& e) {
                std::cerr << "Непойманное исключение типа SomeType: " << e.what() << std::endl;
            } 
            catch (...) {
                std::cerr << "Непойманное исключение неизвестного типа" << std::endl;
            }
        }
        else {
            std::cerr << "Неизвестная ошибка" << std::endl;
        }

        // Всё равно завершим программу.
        std::abort();
    };
    
    // Установим обработчик для функции terminate
    std::set_terminate(terminate_handler);

    // …..
}

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

Остаётся только сохранить всё, что можно, и извиниться перед пользователем за неполадку. А затем выйти из программы окончательно вызовом std::abort().

Базовые исключения стандартной библиотеки

Далеко не всегда есть смысл создавать новый тип исключений, ведь в стандартной библиотеке их и так немало. А если вы всё же создаёте свои исключения, то сделайте их наследниками одного из базовых. Рекомендуется делать все типы исключений прямыми или косвенными наследниками std::exception.

Обратим внимание на одну важную вещь. Все описываемые далее классы не содержат никакой магии. Это обычные и очень простые классы, которые вы могли бы реализовать и самостоятельно. Использовать их можно и без throw, однако смысла в этом немного.

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

#include 
#include 

int main() {
    // Используем std::runtime_error вместо std::string.
    // Но зачем?
    std::runtime_error err("Буря мглою небо кроет");

    std::cout << err.what() << std::endl;
}

Разберём основные типы исключений, описанные в стандартной библиотеке C++.

std::exception

Базовый класс всех исключений стандартной библиотеки. Конструктор не принимает параметров. Имеет метод what(), возвращающий описание исключения. Как правило, используются производные классы, переопределяющие метод what().

std::logic_error : public std::exception

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

Конструктор принимает сообщение в виде std::string, которое будет возвращаться методом what().

// класс копилка
class Moneybox {
public:
    void WithdrawCoin() {
        if (coins_ == 0) {
            throw std::logic_error("В копилке нет денег");
        }
        --coins_;
    }
    void PutCoin() {
        ++coins_;
    }

private:
    int coins_ = 0;
}

Перечислим некоторые производные классы std::logic_error. У всех них похожий интерфейс.

  • std::invalid_argument. Исключение этого типа показывает, что функции передан некорректный аргумент, не соответствующий условиям.
double GetSqrt(double x) {
    return x >= 0 ? sqrt(x) : 
        throw std::invalid_argument("Попытка извлечь квадратный корень из отрицательного числа");
}

Это исключение выбрасывают функции преобразования строки в число, такие как stol, stof, stoul, а также конструктор класса std::bitset:

try {
    int f = std::stoi("abracadabra");
} catch (std::invalid_argument& ex) {
    std::cout << ex.what() << 'n';
}
  • std::length_error. Исключение говорит о том, что превышен лимит вместимости контейнера. Может выбрасываться из методов, меняющих размер контейнеров string и vector. Например resize, reserve, push_back.
  • std::out_of_range. Исключение говорит о том, что некоторое значение находится за пределами допустимого диапазона. Возникает при использовании метода at практически всех контейнеров. Также возникает при использовании функций конвертации в строки в число, таких как stol, stof, stoul. В стандартной библиотеке есть исключение с похожим смыслом — std::range_error.

std::runtime_error : public std::exception

std::runtime_error — ещё один базовый тип для нескольких видов исключений. Он говорит о том, что исключение относится скорее не к предусмотренной ошибке, а к выявленной в процессе выполнения.

При этом, если std::logic_error подразумевает конкретную причину ошибки — нарушение конкретного условия, — то std::runtime_error говорит о том, что что-то идёт не так, но первопричина может быть не вполне очевидна.

Интерфейс такой же, как и у logic_error: класс принимает описание ошибки в конструкторе и переопределяет метод what() базового класса std::exception.

class CommandLineParsingError : public std::runtime_error {
public:
    // этой строкой импортируем конструктор из базового класса:
    using runtime_error::runtime_error;
};

class ZeroDenominatorError : public std::runtime_error {
public:
    // используем готовое сообщение:
    ZeroDenominatorError() : std::runtime_error("Знаменатель не может быть нулём") {
    }
}

Рассмотрим некоторые важные производные классы:

  • std::regex_error. Исключение, возникшее в процессе работы с регулярными выражениями. Например, при неверном синтаксисе регулярного выражения.
  • std::system_error. Широкий класс исключений, связанных с потоками, вводом-выводом или файловой системой.
  • std::format_error. Исключение, возникшее при работе функции std::format.

std::bad_alloc : public std::exception

У std::exception есть и другие наследники. Самый важный — std::bad_alloc. Его может выбрасывать операция new. Это исключение — слабое место многих программ и головная боль многих разработчиков, ведь оно может возникать практически везде — в любом месте, где есть динамическая аллокация. То есть при:

  • вставке в любой контейнер;
  • копировании любого контейнера, например, обычной строки;
  • создании умного указателя unique_ptr или shared_ptr;
  • копировании объекта, содержащего контейнер;
  • прямом вызове new (надеемся, что вы так не делаете);
  • работе с потоками ввода-вывода;
  • работе алгоритмов;
  • вызове корутин;
  • в пользовательских классах и библиотеках — практически при любых операциях.

При обработке bad_alloc нужно соблюдать осторожность и избегать других динамических аллокаций.

#include 
#include 
#include 
#include 

int main() {
    std::vector vec;
    try {
        while (true) {
            vec.push_back(std::string(10000000, 'a'));
        }
    }
    catch (const std::bad_alloc& e) {
        std::cout << "Место закончилось после вставки " << vec.size() << " элементов" << std::endl;
    }
}

Возможный вывод: «Место закончилось после вставки 2640 элементов».

При аллокациях возможна также ошибка std::bad_array_new_length, производная от bad_alloc. Она возникает при попытке выделить слишком большое, слишком маленькое (меньше, чем задано элементов для инициализации) либо отрицательное количество памяти.

Также при аллокации можно запретить new выбрасывать исключение. Для этого пишем (std::nothrow) после new:

int main()
{
    int* m = new (std::nothrow) int [0xFFFFFFFFFFFFFFULL];
    std::cout << m; // выведет 0
    delete[] m;
}

В случае ошибки операция будет возвращать нулевой указатель.

bad_alloc настолько сложно учитывать, что многие даже не пытаются это делать. Мотивация такая: если память закончилась, то всё равно программе делать уже нечего. Лучше поскорей вызвать std::terminate и завершиться.

Заключение

В этой части мы разобрали, как создавать исключения C++, какие они бывают и как с ними работать. Разобрали ключевые слова try, catch и throw.

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

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

C++ позволяет выразительно обрабатывать исключения, он аккуратен при удалении всех объектов и освобождении ресурсов. Будьте аккуратны и вы, и тогда всё получится. Каждому исключению — по обработчику.

Исключения — это лишь одна из многих возможностей C++. Глубже погрузиться в язык и узнать больше о нём, его экосистеме и принципах программирования поможет курс «Разработчик C++».

ГЛАВА 13. Обработка исключительных ситуаций

Исключительная ситуация, или просто исключение,
происходит во время выполнения. Используя под­
систему обработки исключительных ситуаций в С#,
можно обрабатывать структурированным и контроли­
руемым образом ошибки, возникающие при выполнении
программы. Главное преимущество обработки исключи­
тельных ситуаций заключается в том, что она позволяет ав­
томатизировать получение большей части кода, который
раньше приходилось вводить в любую крупную програм­
му вручную для обработки ошибок. Так, если программа
написана на языке программирования без обработки ис­
ключительных ситуаций, то при неудачном выполнении
методов приходится возвращать коды ошибок, которые не­
обходимо проверять вручную при каждом вызове метода.
Это не только трудоемкий, но и чреватый ошибками про­
цесс. Обработка исключительных ситуаций рационализи­
рует весь процесс обработки ошибок, позволяя определить
в программе блок кода, называемый обработчиком исклю­
чений и выполняющийся автоматически, когда возникает
ошибка. Эго избавляет от необходимости проверять вруч­
ную, насколько удачно или неудачно завершилась конкрет­
ная операция либо вызов метода. Если возникнет ошибка,
она будет обработана соответствующим образом обработ­
чиком ошибок.

Обработка исключительных ситуаций важна еще и по­
тому, что в С# определены стандартные исключения для
типичных программных ошибок, например деление на
нуль или выход индекса за границы массива. Для реаги­
рования на подобные ошибки в программе должно быть
организовано отслеживание и обработка соответствующих
исключительных ситуаций. Ведь в конечном счете для успешного программирования
на C# необходимо научиться умело пользоваться подсистемой обработки исключи­
тельных ситуаций.

Класс System.Exception

В C# исключения представлены в виде классов. Все классы исключений должны
быть производными от встроенного в C# класса Exception, являющегося частью про­
странства имен System. Следовательно, все исключения являются подклассами класса
Exception.

К числу самых важных подклассов Exception относится класс SystemException.
Именно от этого класса являются производными все исключения, генерируемые испол­
няющей системой C# (т.е. системой CLR). Класс SystemException ничего не добавляет
к классу Exception, а просто определяет вершину иерархии стандартных исключений.

В среде .NET Framework определено несколько встроенных исключений, являю­
щихся производными от класса SystemException. Например, при попытке выпол­
нить деление на нуль генерируется исключение DivideByZeroException. Как будет
показано далее в этой главе, в C# можно создавать собственные классы исключений,
производные от класса Exception.

Основы обработки исключительных ситуаций

Обработка исключительных ситуаций в C# организуется с помощью четырех клю­
чевых слов: try, catch, throw и finally. Они образуют взаимосвязанную подсистему,
в которой применение одного из ключевых слов подразумевает применение другого.
На протяжении всей этой главы назначение и применение каждого из упомянутых
выше ключевых слов будет рассмотрено во всех подробностях. Но прежде необходимо
дать общее представление о роли каждого из них в обработке исключительных ситуа­
ций. Поэтому ниже кратко описан принцип их действия.

Операторы программы, которые требуется контролировать на появление исключе­
ний, заключаются в блок try. Если внутри блока try возникает исключительная ситуа­
ция, генерируется исключение. Это исключение может быть перехвачено и обработано
каким-нибудь рациональным способом в коде программы с помощью оператора, обо­
значаемого ключевым словом catch. Исключения, возникающие на уровне системы,
генерируются исполняющей системой автоматически. А для генерирования исключе­
ний вручную служит ключевое слово throw. Любой код, который должен быть непре­
менно выполнен после выхода из блока try, помещается в блок finally.

Применение пары ключевых слов try и catch

Основу обработки исключительных ситуаций в C# составляет пара ключевых слов
try и catch. Эти ключевые слова действуют совместно и не могут быть использованы
порознь. Ниже приведена общая форма определения блоков try/catch для обработ­
ки исключительных ситуаций:

try {
    // Блок кода, проверяемый на наличие ошибок.
}
catch (ExcepType1 exOb) {
    // Обработчик исключения типа ExcepTypel.
}
catch (ExcepType2 exOb) {
    // Обработчик исключения типа ExcepType2.
}

где ЕхсерТуре — это тип возникающей исключительной ситуации. Когда исключение
генерируется оператором try, оно перехватывается составляющим ему пару опера­
тором catch, который затем обрабатывает это исключение. В зависимости от типа
исключения выполняется и соответствующий оператор catch. Так, если типы гене­
рируемого исключения и того, что указывается в операторе catch, совпадают, то вы­
полняется именно этот оператор, а все остальные пропускаются. Когда исключение
перехватывается, переменная исключения exOb получает свое значение.

На самом деле указывать переменную ехОb необязательно. Так, ее необязательно
указывать, если обработчику исключений не требуется доступ к объекту исключения,
что бывает довольно часто. Для обработки исключения достаточно и его типа. Именно
поэтому во многих примерах программ, приведенных в этой главе, переменная ехОb
опускается.

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

Простой пример обработки исключительной ситуации

Рассмотрим простой пример, демонстрирующий отслеживание и перехватывание
исключения. Как вам должно быть уже известно, попытка индексировать массив за его
границами приводит к ошибке. Когда возникает подобная ошибка, система CLR гене­
рирует исключение IndexOutOfRangeException, которое определено как стандарт­
ное для среды .NET Framework. В приведенной ниже программе такое исключение
генерируется намеренно и затем перехватывается.

// Продемонстрировать обработку исключительной ситуации.
using System;

class ExcDemol {
    static void Main() {
        int[] nums = new int[4];

        try {
            Console.WriteLine("До генерирования исключения.");

            // Сгенерировать исключение в связи с выходом индекса за границы массива.
            for(int i=0; i < 10; i++) {
                nums[i] = i;
                Console.WriteLine("nums[(0)]: {1}", i, nums[i]);
            }
            Console.WriteLine("He подлежит выводу");
        }
        catch (IndexOutOfRangeException) {
            // Перехватить исключение.
            Console.WriteLine("Индекс вышел за границы массива!");
        }
        Console.WriteLine("После блока перехвата исключения.");
    }
}

При выполнении этой программы получается следующий результат.

До генерирования исключения.
nums[0]: 0
nums[1]: 1
nums[2]: 2
nums[3]: 3
Индекс вышел за границы массива!
После блока перехвата исключения.

В данном примере массив nums типа int состоит из четырех элементов. Но в цикле
for предпринимается попытка проиндексировать этот массив от 0 до 9, что и приво­
дит к появлению исключения IndexOutOfRangeException, когда происходит обра­
щение к элементу массива по индексу 4.

Несмотря на всю свою краткость, приведенный выше пример наглядно демон­
стрирует ряд основных моментов процесса обработки исключительных ситуаций.
Во-первых, код, который требуется контролировать на наличие ошибок, содержится в
блоке try. Во-вторых, когда возникает исключительная ситуация (в данном случае —
при попытке проиндексировать массив nums за его границами в цикле for), в блоке
try генерируется исключение, которое затем перехватывается в блоке catch. В этот
момент выполнение кода в блоке try завершается и управление передается блоку
catch. Это означает, что оператор catch не вызывается специально, а выполнение
кода переходит к нему автоматически. Следовательно, оператор, содержащий метод
WriteLine() и следующий непосредственно за циклом for, где происходит выход
индекса за границы массива, вообще не выполняется. А в задачу обработчика исклю­
чений входит исправление ошибки, приведшей к исключительной ситуации, чтобы
продолжить выполнение программы в нормальном режиме.

Обратите внимание на то, что в операторе catch указан только тип исключения
(в данном случае — IndexOutOfRangeException), а переменная исключения отсут­
ствует. Как упоминалось ранее, переменную исключения требуется указывать лишь
в том случае, если требуется доступ к объекту исключения. В ряде случаев значение
объекта исключения может быть использовано обработчиком исключений для по­
лучения дополнительной информации о самой ошибке, но зачастую для обработки
исключительной ситуации достаточно просто знать, что она произошла. Поэтому
переменная исключения нередко отсутствует в обработчиках исключений, как в рас­
сматриваемом здесь примере.

Как пояснялось ранее, если исключение не генерируется в блоке try, то блок catch
не выполняется, а управление программой передается оператору, следующему после
блока catch. Для того чтобы убедиться в этом, замените в предыдущем примере про­
граммы строку кода

for(int i=0; i < 10; i++) {

на строку

for(int i=0; i < nums.Length; i++) {

Теперь индексирование массива не выходит за его границы в цикле for. Следова­
тельно, никакого исключения не генерируется и блок catch не выполняется.

Второй пример обработки исключительной ситуации

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

В качестве еще одного примера рассмотрим следующую программу, где блок try
помещается в методе Main(). Из этого блока вызывается метод GenException(), в ко­
тором и генерируется исключение IndexOutOfRangeException. Это исключение не
перехватывается методом GenException(). Но поскольку метод GenException() вы­
зывается из блока try в методе Main(), то исключение перехватывается в блоке catch,
связанном непосредственно с этим блоком try.

/* Исключение может быть сгенерировано одним методом
и перехвачено другим. */
using System;

class ExcTest {
    // Сгенерировать исключение.
    public static void GenException() {
        int[] nums = new int[4];

        Console.WriteLine("До генерирования исключения.");

        // Сгенерировать исключение в связи с выходом индекса за границы
        массива.
        for(int i=0; i < 10; i++) {
            nums[i] = i;
            Console.WriteLine("nums [{0}] : {1}", i, nums[i]);
        }

        Console.WriteLine("He подлежит выводу");
    }
}

class ExcDemo2 {
    static void Main() {
        try {
            ExcTest.GenException();
        }
        catch (IndexOutOfRangeException) {
            // Перехватить исключение.
            Console.WriteLine("Индекс вышел за границы массива!");
        }
        Console.WriteLine("После блока перехвата исключения.");
    }
}

Выполнение этой программы дает такой же результат, как и в предыдущем
примере.

До генерирования исключения.
nums[0]: 0
nums[1]: 1
nums[2]: 2
nums[3]: 3
Индекс вышел за границы массива!
После блока перехвата исключения.

Как пояснялось выше, метод GenException() вызывается из блока try, и поэтому
генерируемое им исключение перехватывается не в нем, а в блоке catch внутри мето­
да Main(). А если бы исключение перехватывалось в методе GenException(), оно не
было бы вообще передано обратно методу Main().

Последствия неперехвата исключений

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

// Предоставить исполняющей системе C# возможность самой обрабатывать ошибки.
using System;

class NotHandled {
    static void Main() {
        int[] nums = new int[4];

        Console.WriteLine("До генерирования исключения.");

        // Сгенерировать исключение в связи с выходом индекса за границы массива.
        for(int i=0; i < 10; i++) {
            nums[i] = i;
            Console.WriteLine("nums[{0}]: {1}", i, nums[i]);
        }
    }
}

Когда возникает ошибка индексирования массива, выполнение программы преры­
вается и выдается следующее сообщение об ошибке.

Необработанное исключение: System.IndexOutOfRangeException:
        Индекс находился вне границ массива.
    в NotHandled.Main() в <имя_файла>:строка 16

Это сообщение уведомляет об обнаружении в методе NotHandled.Main() необра­
ботанного исключения типа System.IndexOutOfRangeException, которое связано
с выходом индекса за границы массива.

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

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

// Не сработает!
using System;

class ExcTypeMismatch {
    static void Main() {
        int[] nums = new int[4];
        try {
            Console.WriteLine("До генерирования исключения.");
            // Сгенерировать исключение в связи с выходом индекса за границы массива.
            for(int i=0; i < 10; i++) {
                nums[i] = i;
                Console.WriteLine("nums[{0}]: {1}", i, nums[i]);
            }
            Console.WriteLine("He подлежит выводу");
        }
        /* Если перехват рассчитан на исключение DivideByZeroException,
        то перехватить ошибку нарушения границ массива не удастся. */
        catch (DivideByZeroException) {
            // Перехватить исключение.
            Console.WriteLine("Индекс вышел за границы массива!");
        }
        Console.WriteLine("После блока перехвата исключения.");
    }
}

Вот к какому результату приводит выполнение этой программы.

До генерирования исключения.
nums[0]: 0
nums[1]: 1
nums[2]: 2
nums[3]: 3
Необработанное исключение: System.IndexOutOfRangeException:
        Индекс находился вне границ массива
    в ExcTypeMismatch.Main() в <имя_файла>:строка 18

Как следует из приведенного выше результата, в блоке catch, реагирующем
на исключение DivideByZeroException, не удалось перехватить исключение
IndexOutOfRangeException.

Обработка исключительных ситуаций — “изящный” способ устранения программных ошибок

Одно из главных преимуществ обработки исключительных ситуаций заключается
в том, что она позволяет вовремя отреагировать на ошибку в программе и затем про­
должить ее выполнение. В качестве примера рассмотрим еще одну программу, в кото­
рой элементы одного массива делятся на элементы другого. Если при этом происходит
деление на нуль, то генерируется исключение DivideByZeroException. Обработка
подобной исключительной ситуации заключается в том, что программа уведомляет
об ошибке и затем продолжает свое выполнение. Таким образом, попытка деления
на нуль не приведет к аварийному завершению программы из-за ошибки при ее вы­
полнении. Вместо этого ошибка обрабатывается «изящно», не прерывая выполнение
программы.

// Изящно обработать исключительную ситуацию и продолжить выполнение программы.
using System;

class ExcDemo3 {
    static void Main() {
        int[] numer = { 4, 8, 16, 32, 64, 128 };
        int[] denom = { 2, 0, 4, 4, 0, 8 };

        for(int i=0; i < numer.Length; i++) {
            try {
                Console.WriteLine(numer[i] + " / " +
                                denom[i] + " равно " +
                                numer[i]/denom[i]);
            }
            catch (DivideByZeroException) {
                // Перехватить исключение.
                Console.WriteLine("Делить на нуль нельзя!");
            }
        }
    }
}

Ниже приведен результат выполнения этой программы.

4/2 равно 2
Делить на нуль нельзя!
16/4 равно 4
32/4 равно 8
Делить на нуль нельзя!
128 / 8 равно 16

Из данного примера следует еще один важный вывод: как только исключение обра­
ботано, оно удаляется из системы. Поэтому в приведенной выше программе проверка
ошибок в блоке try начинается снова на каждом шаге цикла for, при условии, что все
предыдущие исключительные ситуации были обработаны. Это позволяет обрабаты­
вать в программе повторяющиеся ошибки.

Применение нескольких операторов catch

С одним оператором try можно связать несколько операторов catch. И на прак­
тике это делается довольно часто. Но все операторы catch должны перехватывать ис­
ключения разного типа. В качестве примера ниже приведена программа, в которой
перехватываются ошибки выхода за границы массива и деления на нуль.

// Использовать несколько операторов catch.
using System;

class ExcDemo4 {
    static void Main() {
        // Здесь массив numer длиннее массива denom.
        int[] numer = { 4, 8, 16, 32, 64, 128, 256, 512 };
        int[] denom = { 2, 0, 4, 4, 0, 8 };

        for(int i=0; i < numer.Length; i++) {
            try {
                Console.WriteLine(numer[i] + " / " +
                                denom[i] + " равно " +
                                numer[i]/denom[i]);
            }
            catch (DivideByZeroException) {
                Console.WriteLine("Делить на нуль нельзя!");
            }
            catch (IndexOutOfRangeException) {
                Console.WriteLine("Подходящий элемент не найден.");
            }
        }
    }
}

Вот к какому результату приводит выполнение этой программы.

4/2 равно 2
Делить на нуль нельзя!
16/4 равно 4
32/4 равно 8
Делить на нуль нельзя!
128 / 8 равно 16
Подходящий элемент не найден.
Подходящий элемент не найден.

Как следует из приведенного выше результата, каждый оператор catch реагирует
только на свой тип исключения.

Вообще говоря, операторы catch выполняются по порядку их следования в про­
грамме. Но при этом выполняется только один блок catch, в котором тип исклю­
чения совпадает с типом генерируемого исключения. А все остальные блоки catch
пропускаются.

Перехват всех исключений

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

catch {
    // обработка исключений
}

С помощью такой формы создается «универсальный» обработчик всех исключе­
ний, перехватываемых в программе.

Ниже приведен пример такого «универсального» обработчика исключений. Об­
ратите внимание на то, что он перехватывает и обрабатывает оба исключения,
IndexOutOfRangeException и DivideByZeroException, генерируемых в программе.

// Использовать "универсальный" обработчик исключений.
using System;

class ExcDemo5 {
    static void Main() {
        // Здесь массив numer длиннее массива denom.
        int[] numer = { 4, 8, 16, 32, 64, 128, 256, 512 };
        int[] denom = { 2, 0, 4, 4, 0, 8 );

        for(int i=0; i < numer.Length; i++) {
            try {
                Console.WriteLine(numer[i] + " / " +
                                denom[i] + " равно " +
                                numer[i]/denom[i]);
            }
            catch { // "Универсальный" перехват.
                Console.WriteLine("Возникла некоторая исключительная ситуация.");
            }
        }
    }
}

При выполнении этой программы получается следующий результат.

4/2 равно 2
Возникла некоторая исключительная ситуация.
16/4 равно 4
32/4 равно 8
Возникла некоторая исключительная ситуация.
128 / 8 равно 16
Возникла некоторая исключительная ситуация.
Возникла некоторая исключительная ситуация.

Применяя «универсальный» перехват, следует иметь в виду, что его блок должен
располагаться последним по порядку среди всех блоков catch.

ПРИМЕЧАНИЕ
В подавляющем большинстве случаев «универсальный» обработчик исключений (не при­
меняется. Как правило, исключения, которые могут быть сгенерированы в коде, обрабаты­
ваются по отдельности. Неправильное использование “универсального” обработчика может
привести к тому, что ошибки, перехватывавшиеся при тестировании программы, маскируют­
ся. Кроме того, организовать надлежащую обработку всех исключительных ситуаций в одном
обработчике не так-то просто. Иными словами, “универсальный» обработчик исключений
может оказаться пригодным лишь в особых случаях, например в инструментальном средстве
анализа кода во время выполнения.

Вложение блоков try

Один блок try может быть вложен в другой. Исключение, генерируемое во вну­
треннем блоке try и не перехваченное в соответствующем блоке catch, передается во
внешний блок try. В качестве примера ниже приведена программа, в которой исклю­
чение IndexOutOfRangeException перехватывается не во внутреннем, а во внешнем
блоке try.

// Использовать вложенный блок try.
using System;

class NestTrys {
    static void Main() {
        // Здесь массив numer длиннее массива denom.
        int[] numer = { 4, 8, 16, 32, 64, 128, 256, 512 };
        int[] denom = ( 2, 0, 4, 4, 0, 8 );

        try { // внешний блок try
            for(int i=0; i < numer.Length; i++) {
                try { // вложенный блок try
                    Console.WriteLine(numer[i] + " / " +
                                    denom[i] + " равно " +
                                    numer[i]/denom[i]);
                }
                catch (DivideByZeroException) {
                    Console.WriteLine("Делить на нуль нельзя!");
                }
            }
        }
        catch (IndexOutOfRangeException) {
            Console.WriteLine("Подходящий элемент не найден.");
            Console.WriteLine("Неисправимая ошибка - программа прервана.");
        }
    }
}

Выполнение этой программы приводит к следующему результату.

4/2 равно 2
Делить на нуль нельзя!
16/4 равно 4
32/4 равно 8
Делить на нуль нельзя!
128 / 8 равно 16
Подходящий элемент не найден.
Неисправимая ошибка - программа прервана.

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

Безусловно, приведенный выше пример демонстрирует далеко не единственное
основание для применения вложенных блоков try, тем не менее из него можно сделать
важный общий вывод. Вложенные блоки try нередко применяются для обработки раз­
личных категорий ошибок разными способами. В частности, одни ошибки считаются
неисправимыми и не подлежат исправлению, а другие ошибки незначительны и могут
быть обработаны немедленно. Как правило, внешний блок try служит для обнаруже­
ниям обработки самых серьезных ошибок, а во внутренних блоках try обрабатываются
менее серьезные ошибки. Кроме того, внешний блок try может стать «универсальным»
для тех ошибок, которые не подлежат обработке во внутреннем блоке.

Генерирование исключений вручную

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

где в качестве exceptOb должен быть обозначен объект класса исключений, произво­
дного от класса Exception.

Ниже приведен пример программы, в которой демонстрируется применение опе­
ратора throw для генерирования исключения DivideByZeroException.

// Сгенерировать исключение вручную.
using System;

class ThrowDemo {
    static void Main() {
        try {
            Console.WriteLine("До генерирования исключения.");
            throw new DivideByZeroException();
        }
        catch (DivideByZeroException) {
            Console.WriteLine("Исключение перехвачено.");
        }
        Console.WriteLine("После пары операторов try/catch.");
    }
}

Вот к какому результату приводит выполнение этой программы.

До генерирования исключения.
Исключение перехвачено.
После пары операторов try/catch.

Обратите внимание на то, что исключение DivideByZeroException было сге­
нерировано с использованием ключевого слова new в операторе throw. Не следует
забывать, что в данном случае генерируется конкретный объект, а следовательно, он
должен быть создан перед генерированием исключения. Это означает, что сгенериро­
вать исключение только по его типу нельзя. В данном примере для создания объекта
DivideByZeroException был автоматически вызван конструктор, используемый по
умолчанию, хотя для генерирования исключений доступны и другие конструкторы.

Повторное генерирование исключений

Исключение, перехваченное в одном блоке catch, может быть повторно сгенери­
ровано в другом блоке, чтобы быть перехваченным во внешнем блоке catch. Наиболее
вероятной причиной для повторного генерирования исключения служит предоставле­
ние доступа к исключению нескольким обработчикам. Допустим, что один обработчик
оперирует каким-нибудь одним аспектом исключения, а другой обработчик — другим
его аспектом. Для повторного генерирования исключения достаточно указать опера­
тор throw без сопутствующего выражения, как в приведенной ниже форме.

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

В приведенном ниже примере программы демонстрируется повтор­
ное генерирование исключения. В данном случае генерируется исключение
IndexOutOfRangeException.

// Сгенерировать исключение повторно.
using System;

class Rethrow {
    public static void GenException() {
        // Здесь массив numer длиннее массива denom.
        int[] numer = { 4, 8, 16, 32, 64, 128, 256, 512 };
        int[] denom = { 2, 0, 4, 4, 0, 8 };

        for(int i=0; i<numer.Length; i++) {
            try {
                Console.WriteLine(numer[i] + " / " +
                                denom[i] + " равно " +
                                numer[i]/denom[i]);
            }
            catch (DivideByZeroException) {
                Console.WriteLine("Делить на нуль нельзя!");
            }
            catch (IndexOutOfRangeException) {
                Console.WriteLine("Подходящий элемент не найден.");
                throw; // сгенерировать исключение повторно
            }
        }
    }
}

class RethrowDemo {
    static void Main() {
        try {
            Rethrow.GenException();
        }
        catch(IndexOutOfRangeException) {
            // перехватить исключение повторно
            Console.WriteLine("Неисправимая ошибка - программа прервана.");
        }
    }
}

В этом примере программы ошибки из-за деления на нуль обрабатываются локаль­
но в методе GenException(), но ошибка выхода за границы массива генерируется
повторно. В данном случае исключение IndexOutOfRangeException обрабатывается
в методе Main().

Использование блока finally

Иногда требуется определить кодовый блок, который будет выполняться после вы­
хода из блока try/catch. В частности, исключительная ситуация может возникнуть
в связи с ошибкой, приводящей к преждевременному возврату из текущего метода.
Но в этом методе мог быть открыт файл, который нужно закрыть, или же установлено
сетевое соединение, требующее разрывания. Подобные ситуации нередки в програм­
мировании, и поэтому для их разрешения в C# предусмотрен удобный способ: вос­
пользоваться блоком finally.

Для того чтобы указать кодовый блок, который должен выполняться после блока
try/catch, достаточно вставить блок finally в конце последовательности операторов
try/catch. Ниже приведена общая форма совместного использования блоков try/
catch и finally.

try {
    // Блок кода, предназначенный для обработки ошибок.
}
catch (ExcepType1 exOb) {
    // Обработчик исключения типа ExcepType1.
}
catch (ExcepType2 ехОb) {
    // Обработчик исключения типа ЕхсерТуре2.
}
finally {
    // Код завершения обработки исключений.
}

Блок finally будет выполняться всякий раз, когда происходит выход из блока try/
catch, независимо от причин, которые к этому привели. Это означает, что если блок
try завершается нормально или по причине исключения, то последним выполняется
код, определяемый в блоке finally. Блок finally выполняется и в том случае, если
любой код в блоке try или в связанных с ним блоках catch приводит к возврату из
метода.

Ниже приведен пример применения блока finally.

// Использовать блок finally.
using System;

class UseFinally {
    public static void GenException(int what) {
        int t;
        int[] nums = new int[2];

        Console.WriteLine("Получить " + what);
        try {
            switch(what) {
                case 0:
                    t = 10 / what; // сгенерировать ошибку из-за деления на нуль
                    break;
                case 1:
                    nums[4] = 4; // сгенерировать ошибку индексирования массива
                    break;
                case 2:
                    return; // возврат из блока try
            }
        }
        catch (DivideByZeroException) {
            Console.WriteLine("Делить на нуль нельзя!");
            return; // возврат из блока catch
        }
        catch (IndexOutOfRangeException) {
            Console.WriteLine("Совпадающий элемент не найден.");
        }
        finally {
            Console.WriteLine("После выхода из блока try.");
        }
    }
}

class FinallyDemo {
    static void Main() {
        for(int i=0; i < 3; i++) {
            UseFinally.GenException(i);
            Console.WriteLine();
        }
    }
}

Вот к какому результату приводит выполнение этой программы.

Получить 0
Делить на нуль нельзя
После выхода из блока try.

Получить 1
Совпадающий элемент не найден.
После выхода из блока try.

Получить 2
После выхода из блока try.

Как следует из приведенного выше результата, блок finally выполняется независи­
мо от причины выхода из блока try.

И еще одно замечание: с точки зрения синтаксиса блок finally следует после блока
try, и формально блоки catch для этого не требуются. Следовательно, блок finally
можно ввести непосредственно после блока try, опустив блоки catch. В этом случае
блок finally начнет выполняться сразу же после выхода из блока try, но исключения
обрабатываться не будут.

Подробное рассмотрение класса Exception

В приведенных выше примерах исключения только перехватывались, но никакой
существенной обработке они не подвергались. Как пояснялось выше, в операторе
catch допускается указывать тип и переменную исключения. Переменная получает
ссылку на объект исключения. Во всех исключениях поддерживаются члены, опреде­
ленные в классе Exception, поскольку все исключения являются производными от
этого класса. В этом разделе будет рассмотрен ряд наиболее полезных членов и кон­
структоров класса Exception и приведены конкретные примеры использования пере­
менной исключения.

В классе Exception определяется ряд свойств. К числу самых интересных отно­
сятся три свойства: Message, StackTrace и TargetSite. Все эти свойства доступны
только для чтения. Свойство Message содержит символьную строку, описывающую
характер ошибки; свойство StackTrace — строку с вызовами стека, приведшими к ис­
ключительной ситуации, а свойство TargetSite получает объект, обозначающий ме­
тод, сгенерировавший исключение.

Кроме того, в классе Exception определяется ряд методов. Чаще всего приходится
пользоваться методом ToString(), возвращающим символьную строку с описанием
исключения. Этот метод автоматически вызывается, например, при отображении ис­
ключения с помощью метода WriteLine().

Применение всех трех упомянутых выше свойств и метода из класса Exception
демонстрируется в приведенном ниже примере программы.

// Использовать члены класса Exception.
using System;
class ExcTest {
    public static void GenException() {
        int[] nums = new int[4];
        Console.WriteLine("До генерирования исключения.");
        // Сгенерировать исключение в связи с выходом за границы массива.
        for(int i=0; i < 10; i++) {
            nums[i] = i;
            Console.WriteLine("nums[{0}]: (1)", i, nums[i]);
        }
        Console.WriteLine("He подлежит выводу");
    }
}

class UseExcept {
    static void Main() {
        try {
            ExcTest.GenException();
        }
        catch (IndexOutOfRangeException exc) {
            Console.WriteLine("Стандартное сообщение таково: ");
            Console.WriteLine(exc); // вызвать метод ToString()
            Console.WriteLine("Свойство StackTrace: " + exc.StackTrace);
            Console.WriteLine("Свойство Message: " + exc.Message);
            Console.WriteLine("Свойство TargetSite: " + exc.TargetSite);
        }
        Console.WriteLine("После блока перехвата исключения.");
    }
}

При выполнении этой программы получается следующий результат.

До генерирования исключения.
nums[0]: 0
nums[1]: 1
nums[2]: 2
nums[3]: 3
Стандартное сообщение таково: System.IndexOutOfRangeException: Индекс находился
вне границ массива.
    в ExcTest.genException() в <имя_файла>:строка 15
    в UseExcept.Main() в <имя_файла>:строка 29
Свойство StackTrace:в ExcTest.genException() в <имя_файла>:строка 15
    в UseExcept.Main()в <имя_файла>:строка 29
Свойство Message: Индекс находился вне границ массива.
Свойство TargetSite: Void genException()
После блока перехвата исключения.

В классе Exception определяются четыре следующих конструктора.

public Exception()
public Exception(string сообщение)
public Exception(string сообщение, Exception внутреннее_исключение)
protected Exception(System.Runtime.Serialization.SerializationInfo информация,
    System.Runtime.Serialization.StreamingContext контекст)

Первый конструктор используется по умолчанию. Во втором конструкторе ука­
зывается строка сообщение, связанная со свойством Message, которое имеет отно­
шение к генерируемому исключению. В третьем конструкторе указывается так на­
зываемое внутреннее исключение. Этот конструктор используется в том случае, когда
одно исключение порождает другое, причем внутреннее_исключение обозначает
первое исключение, которое будет пустым, если внутреннее исключение отсутствует.
(Если внутреннее исключение присутствует, то оно может быть получено из свойства
InnerException, определяемого в классе Exception.) И последний конструктор об­
рабатывает исключения, происходящие дистанционно, и поэтому требует десериали­
зации.

Следует также заметить, что в четвертом конструкторе класса Exception типы
SerializationInfo и StreamingContext относятся к пространству имен System.
Runtime.Serialization.

Наиболее часто используемые исключения

В пространстве имен System определено несколько стандартных, встроенных ис­
ключений. Все эти исключения являются производными от класса SystemException,
поскольку они генерируются системой CLR при появлении ошибки во время выпол­
нения. В табл. 13.1 перечислены некоторые наиболее часто используемые стандартные
исключения.

Таблица 13.1. Наиболее часто используемые исключения, определенные в пространстве имен System

Исключение Значение
ArrayTypeMismatchException Тип сохраняемого значения несовместим с типом массива
DivideByZeroException Попытка деления на нуль
IndexOutOfRangeException Индекс оказался за границами массива
InvalidCastException Неверно выполнено динамическое приведение типов
OutOfMemoryException Недостаточно свободной памяти для дальнейшего выполнения программы. Это исключение может быть, например, сгенерировано, если для создания объекта с помощью оператора new не хватает памяти
OverflowException Произошло арифметическое переполнение
NullReferenceException Попытка использовать пустую ссылку, т.е. ссылку, которая не указывает ни на один из объектов

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

// Продемонстрировать обработку исключения NullReferenceException.
using System;

class X {
    int x;

    public X(int a) {
        x = a;
    }

    public int Add(X o) {
        return x + o.x;
    }
}

// Продемонстрировать генерирование и обработку
// исключения NullReferenceException.
class NREDemo {
    static void Main() {
        X p = new X(10);
        X q = null; // присвоить явным образом пустое значение переменной q
        int val;
        try {
            val = p.Add(q); // эта операция приведет к исключительной ситуации
        } catch (NullReferenceException) {
            Console.WriteLine("Исключение NullReferenceException!");
            Console.WriteLine("Исправление ошибки...n");
            // А теперь исправить ошибку.
            q = new X(9);
            val = p.Add(q);
        }
        Console.WriteLine("Значение val равно {0}", val);
    }
}

Вот к какому результату приводит выполнение этой программы.

Исключение NullReferenceException!
Исправление ошибки...

Значение val равно 19

В приведенном выше примере программы создается класс X, в котором определя­
ются член х и метод Add(), складывающий значение члена х в вызывающем объекте
со значением члена х в объекте, передаваемом этому методу в качестве параметра. Оба
объекта класса X создаются в методе Main(). Первый из них (переменная р) инициа­
лизируется, а второй (переменная q) — нет. Вместо этого переменной q присваивается
пустое значение. Затем вызывается метод р.Add() с переменной q в качестве аргумен­
та. Но поскольку переменная q не ссылается ни на один из объектов, то при попытке
получить значение члена q.х генерируется исключение NullReferenceException.

Получение производных классов исключений

Несмотря на то что встроенные исключения охватывают наиболее распространен­
ные программные ошибки, обработка исключительных ситуаций в C# не ограничива­
ется только этими ошибками. В действительности одна из сильных сторон принятого
в C# подхода к обработке исключительных ситуаций состоит в том, что в этом языке
допускается использовать исключения, определяемые пользователем, т.е. тем, кто про­
граммирует на С#. В частности, такие специальные исключения можно использовать
для обработки ошибок в собственном коде, а создаются они очень просто. Для этого
достаточно определить класс, производный от класса Exception. В таких классах со­
всем не обязательно что-то реализовывать — одного только их существования в систе­
ме типов уже достаточно, чтобы использовать их в качестве исключений.

ПРИМЕЧАНИЕ
В прошлом специальные исключения создавались как производные от класса
Application.Exception, поскольку эта иерархия классов была первоначально зарезер­
вирована для исключений прикладного характера. Но теперь корпорация Microsoft не реко­
мендует этого делать, а вместо этого получать исключения, производные от класса Exception.
Именно по этой причине данный подход и рассматривается в настоящей книге.

Создаваемые пользователем классы будут автоматически получать свойства и мето­
ды, определенные в классе Exception и доступные для них. Разумеется, любой из этих
членов класса Exception можно переопределить в создаваемых классах исключений.
Когда создается собственный класс исключений, то, как правило, желательно, что­
бы в нем поддерживались все конструкторы, определенные в классе Exception. В про­
стых специальных классах исключений этого нетрудно добиться, поскольку для этого
достаточно передать подходящие аргументы соответствующему конструктору класса
Exception, используя ключевое слово base. Но формально нужно предоставить толь­
ко те конструкторы, которые фактически используются в программе.

Рассмотрим пример программы, в которой используется исключение специального
типа. Напомним, что в конце главы 10 был разработан класс RangeArray, поддержи­
вающий одномерные массивы, в которых начальный и конечный индексы определяют­
ся пользователем. Так, например, вполне допустимым считается массив, индексируе­
мый в пределах от -5 до 27. Если же индекс выходил за границы массива, то для обра­
ботки этой ошибки в классе RangeArray была определена специальная переменная.
Такая переменная устанавливалась и проверялась после каждой операции обращения
к массиву в коде, использовавшем класс RangeArray. Безусловно, такой подход к об­
работке ошибок «неуклюж» и чреват дополнительными ошибками. В приведенном
ниже улучшенном варианте класса RangeArray обработка ошибок нарушения границ
массива выполняется более изящным и надежным способом с помощью специально
генерируемого исключения.

// Использовать специальное исключение для обработки
// ошибок при обращении к массиву класса RangeArray.
using System;

// Создать исключение для класса RangeArray.
class RangeArrayException : Exception {
/* Реализовать все конструкторы класса Exception. Такие конструкторы просто
реализуют конструктор базового класса. А поскольку класс исключения
RangeArrayException ничего не добавляет к классу Exception, то никаких
дополнительных действий не требуется. */
public RangeArrayException() : base)) { }

public RangeArrayException(string str) : base(str) { }

public RangeArrayException(
string str, Exception inner) : base (str, inner) { }

protected RangeArrayException(
System.Runtime.Serialization.SerializationInfo si,
System.Runtime.Serialization.StreamingContext sc) :
base(si, sc) { }

// Переопределить метод ToString() для класса исключения RangeArrayException.
public override string ToString() {
    return Message;
}

}

// Улучшенный вариант класса RangeArray.
class RangeArray {
// Закрытые данные.
int[] a; // ссылка на базовый массив
int lowerBound; // наименьший индекс
int upperBound; // наибольший индекс

// Автоматически реализуемое и доступное только для чтения свойство Length.
public int Length { get; private set; }
// Построить массив по заданному размеру

public RangeArray(int low, int high) {
    high++;
    if(high <= low) {
        throw new RangeArrayException("Нижний индекс не меньше верхнего.");
    }
    а = new int[high - low];
    Length = high - low;
    lowerBound = low;
    upperBound = --high;
}

// Это индексатор для класса RangeArray.
public int this[int index] {
    // Это аксессор get.
    get {
        if(ok(index)) {
            return a[index - lowerBound];
        } else {
            throw new RangeArrayException("Ошибка нарушения границ.");
        }
    }
    // Это аксессор set.
    set {
        if(ok(index)) {
            a[index - lowerBound] = value;
        }
        else throw new RangeArrayException("Ошибка нарушения границ.");
    }
}

// Возвратить логическое значение true, если
// индекс находится в установленных границах.
private bool ok(int index) {
    if(index >= lowerBound S index <= upperBound) return true;
    return false;
}

}

// Продемонстрировать применение массива с произвольно
// задаваемыми пределами индексирования.
class RangeArrayDemo {
static void Main() {
try {
RangeArray ra = new RangeArray(-5, 5);
RangeArray ra2 = new RangeArray(1, 10);

        // Использовать объект ra в качестве массива.
        Console.WriteLine("Длина массива ra: " + ra.Length);
        for(int i = -5; i <= 5; i++)
            ra[i] = i;

        Console.Write("Содержимое массива ra: ");
        for(int i = -5; i <= 5; i++)
            Console.Write(ra[i] + " ");

        Console.WriteLine("n");

        // Использовать объект ra2 в качестве массива.
        Console.WriteLine("Длина массива ra2: " + ra2.Length);
        for(int i = 1; i <= 10; i++)
            ra2[i] = i;

        Console.Write("Длина массива ra2: ");
        for(int i = 1; i <= 10; i++)
            Console.Write(ra2[i] + " ");

        Console.WriteLine("n");
    } catch (RangeArrayException exc) {
        Console.WriteLine(exc);
    }

    // А теперь продемонстрировать обработку некоторых ошибок.
    Console.WriteLine("Сгенерировать ошибки нарушения границ.");

    // Использовать неверно заданный конструктор.
    try {
        RangeArray ra3 = new RangeArray(100, -10); // Ошибка!
    } catch (RangeArrayException exc) {
        Console.WriteLine(exc);
    }

    // Использовать неверно заданный индекс.
    try {
        RangeArray ra3 = new RangeArray(-2, 2);

        for(int i = -2; i <= 2; i++)
            ra3[i] = i;

        Console.Write("Содержимое массива ra3: ");
        for(int i = -2; i <= 10; i++) // сгенерировать ошибку нарушения границ
            Console.Write(ra3[i] + " ");
    } catch (RangeArrayException exc) {
        Console.WriteLine(exc);
    }
}

}

После выполнения этой программы получается следующий результат.

Длина массива ra: 11
Содержимое массива ra: -5 -4 -3 -2 -1 0 1 2 3 4 5

Длина массива ra2: 10
Содержимое массива ra2: 1 2 3 4 5 6 7 8 9 10

Сгенерировать ошибки нарушения границ.
Нижний индекс не меньше верхнего.
Содержимое массива ra3: -2 -1 0 1 2 Ошибка нарушения границ.

Когда возникает ошибка нарушения границ массива класса RangeArray, генери­
руется объект типа RangeArrayException. В классе RangeArray это может произой­
ти в трех следующих местах: в аксессоре get индексатора, в аксессоре set индексатора
и в конструкторе класса RangeArray. Для перехвата этих исключений подразумева­
ется, что объекты типа RangeArray должны быть сконструированы и доступны из
блока try, что и продемонстрировано в приведенной выше программе. Используя
специальное исключение для сообщения об ошибках, класс RangeArray теперь дей­
ствует как один из встроенных в C# типов данных, и поэтому он может быть полностью
интегрирован в механизм обработки ошибок, обнаруживаемых в программе.

Обратите внимание на то, что в теле конструкторов класса исключения
RangeArrayException отсутствуют какие-либо операторы, но вместо этого они про­
сто передают свои аргументы классу Exception, используя ключевое слово base. Как
пояснялось ранее, в тех случаях, когда производный класс исключений не дополняет
функции базового класса, весь процесс создания исключений можно поручить кон­
структорам класса Exception. Ведь производный класс исключений совсем не обяза­
тельно должен чем-то дополнять функции, наследуемые от класса Exception.

Прежде чем переходить к дальнейшему чтению, попробуйте немного поэксперимен­
тировать с приведенной выше программой. В частности, попробуйте закомментировать
переопределение метода ToString() и понаблюдайте за результатами. Кроме того, по­
пытайтесь создать исключение, используя конструктор, вызываемый по умолчанию, и
посмотрите, какое сообщение при этом сформируется стандартными средствами С#.

## Перехват исключений производных классов
При попытке перехватить типы исключений, относящихся как к базовым, так
и к производным классам, следует особенно внимательно соблюдать порядок следо­
вания операторов catch, поскольку перехват исключения базового класса будет со­
впадать с перехватом исключений любых его производных классов. Например, класс
Exception является базовым для всех исключений, и поэтому вместе с исключением
типа Exception могут быть перехвачены и все остальные исключения производных от
него классов. Конечно, для более четкого перехвата всех исключений можно воспользо­
ваться упоминавшейся ранее формой оператора catch без указания конкретного типа
исключения. Но вопрос перехвата исключений производных классов становится весьма
актуальным и в других ситуациях, особенно при создании собственных исключений.

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

В приведенном ниже примере программы создаются два класса исключений:
ExceptA и ExceptB. Класс ExceptA является производным от класса Exception,
а класс ExceptB — производным от класса ExceptA. Затем в программе генерируются
исключения каждого типа. Ради краткости в классах специальных исключений предо­
ставляется только один конструктор, принимающий символьную строку, описываю­
щую исключение. Но при разработке программ коммерческого назначения в классах
специальных исключений обычно требуется предоставлять все четыре конструктора,
определяемых в классе Exception.

// Исключения производных классов должны появляться до
// исключений базового класса.
using System;

// Создать класс исключения.
class ExceptA : Exception {
public ExceptA(string str) : base(str) { }

public override string ToString() {
    return Message;
}

}

// Создать класс исключения, производный от класса ExceptA.
class ExceptB : ExceptA {
public ExceptB(string str) : base(str) { }

public override string ToString() {
    return Message;
}

}

class OrderMatters {
static void Main() {
for(int x = 0; x < 3; x++) {
try {
if(x==0) throw new ExceptA(«Перехват исключения типа ExceptA»);
else if(x==1) throw new ExceptB(«Перехват исключения типа
ExceptB»);
else throw new Exception));
}
catch (ExceptB exc) {
Console.WriteLine(exc);
}
catch (ExceptA exc) {
Console.WriteLine(exc);
}
catch (Exception exc) {
Console.WriteLine(exc);
}
}
}
}

Вот к какому результату приводит выполнение этой программы.

Перехват исключения типа ExceptA.
Перехват исключения типа ExceptB.
System.Exception: Выдано исключение типа «System.Exception».
в OrderMatters.Main() в <имя_файла>:строка 36

Обратите внимание на порядок следования операторов catch. Именно в таком по­
рядке они и должны выполняться. Класс ExceptB является производным от класса
ExceptA, поэтому исключение типа ExceptB должно перехватываться до исключения
типа ExceptA. Аналогично, исключение типа Exception (т.е. базового класса для всех
исключений) должно перехватываться последним. Для того чтобы убедиться в этом,
измените порядок следования операторов catch. В итоге это приведет к ошибке во
время компиляции.

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

## Применение ключевых слов checked и unchecked
В C# имеется специальное средство, связанное с генерированием исключений, воз­
никающих при переполнении в арифметических вычислениях. Как вам должно быть
уже известно, результаты некоторых видов арифметических вычислений могут превы­
шать диапазон представления чисел для типа данных, используемого в вычислении.
В этом случае происходит так называемое переполнение. Рассмотрим в качестве при­
мера следующий фрагмент кода.

byte a, b, result;
а = 127;
b = 127;
result = (byte)(а * b);

В этом коде произведение значений переменных а и b превышает диапазон пред­
ставления чисел для типа byte. Следовательно, результат вычисления данного вы­
ражения приводит к переполнению для типа данных, сохраняемого в переменной
result.

В C# допускается указывать, будет ли в коде сгенерировано исключение при пере­
полнении, с помощью ключевых слов checked и unchecked. Так, если требуется ука­
зать, что выражение будет проверяться на переполнение, следует использовать клю­
чевое слово checked, а если требуется проигнорировать переполнение — ключевое
слово unchecked. В последнем случае результат усекается, чтобы не выйти за пределы
диапазона представления чисел для целевого типа выражения.

У ключевого слова checked имеются две общие формы. В одной форме проверя­
ется конкретное выражение, и поэтому она называется операторной. А в другой форме
проверяется блок операторов, и поэтому она называется блочной. Ниже приведены обе
формы:

checked (выражение)

checked {
// проверяемые операторы
}

где выражение обозначает проверяемое выражение. Если вычисление прове­
ряемого выражения приводит к переполнению, то генерируется исключение
OverflowException.

У ключевого слова unchecked также имеются две общие формы. В первой, опера­
торной форме переполнение игнорируется при вычислении конкретного выражения.
А во второй, блочной форме оно игнорируется при выполнении блока операторов:

unchecked (выражение)

unchecked {
// операторы, для которых переполнение игнорируется
}

где выражение обозначает конкретное выражение, при вычислении которого перепол­
нение игнорируется. Если же в непроверяемом выражении происходит переполнение,
то результат его вычисления усекается.

Ниже приведен пример программы, в котором демонстрируется применение клю­
чевых слов checked и unchecked.

// Продемонстрировать применение ключевых слов checked и unchecked.
using System;

class CheckedDemo {
static void Main() {
byte a, b;
byte result;
a = 127;
b = 127;
try {
result = unchecked((byte)(a * b));
Console.WriteLine(«Непроверенный на переполнение результат: » +
result);
result = checked((byte)(a * b)); // эта операция приводит к
// исключительной ситуации
Console.WriteLine(«Проверенный на переполнение результат: » +
result); //не подлежит выполнению
}
catch (OverflowException exc) {
Console.WriteLine(exc);
}
}
}

При выполнении этой программы получается следующий результат.

Непроверенный на переполнение результат: 1
System.OverflowException: Переполнение в результате
выполнения арифметической операции.
в CheckedDemo.Main() в <имя_файла>:строка 20

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

В представленном выше примере программы было продемонстрировано приме­
нение ключевых слов checked и unchecked в одном выражении. А в следующем при­
мере программы показывается, каким образом проверяется и не проверяется на пере­
полнение целый блок операторов.

// Продемонстрировать применение ключевых слов checked
// и unchecked в блоке операторов.
using System;

class CheckedBlocks {
static void Main() {
byte a, b;
byte result;
a = 127;
b = 127;
try {
unchecked {
a = 127;
b = 127;
result = unchecked((byte)(a * b));
Console.WriteLine(«Непроверенный на переполнение результат: » +
result);

            а = 125;
            b = 5;
            result = unchecked((byte)(a * b));
            Console.WriteLine("Непроверенный на переполнение результат: " +
                            result);
        }

        checked {
            a = 2;
            b = 7;
            result = checked((byte)(a * b)); // верно
            Console.WriteLine("Проверенный на переполнение результат: " +
                            result);

            а = 127;
            b = 127;
            result = checked((byte)(a * b)); // эта операция приводит к
            // исключительной ситуации
            Console.WriteLine("Проверенный на переполнение результат: " +
                            result); // не подлежит выполнению
        }
    }
    catch (OverflowException exc) {
        Console.WriteLine(exc);
    }
}

}

Результат выполнения этой программы приведен ниже.

Непроверенный на переполнение результат: 1
Непроверенный на переполнение результат: 113
Проверенный на переполнение результат: 14
System.OverflowException: Переполнение в результате
выполнения арифметической операции.
в CheckedDemo.Main() в <имя_файма>:строка 41

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

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

std::exception is the class whose only purpose is to serve as the base class in the exception hierarchy. It has no other uses. In other words, conceptually it is an abstract class (even though it is not defined as abstract class in C++ meaning of the term).

std::runtime_error is a more specialized class, descending from std::exception, intended to be thrown in case of various runtime errors. It has a dual purpose. It can be thrown by itself, or it can serve as a base class to various even more specialized types of runtime error exceptions, such as std::range_error, std::overflow_error etc. You can define your own exception classes descending from std::runtime_error, as well as you can define your own exception classes descending from std::exception.

Just like std::runtime_error, standard library contains std::logic_error, also descending from std::exception.

The point of having this hierarchy is to give user the opportunity to use the full power of C++ exception handling mechanism. Since ‘catch’ clause can catch polymorphic exceptions, the user can write ‘catch’ clauses that can catch exception types from a specific subtree of the exception hierarchy. For example, catch (std::runtime_error& e) will catch all exceptions from std::runtime_error subtree, letting all others to pass through (and fly further up the call stack).

P.S. Designing a useful exception class hierarchy (that would let you catch only the exception types you are interested in at each point of your code) is a non-trivial task. What you see in standard C++ library is one possible approach, offered to you by the authors of the language. As you see, they decided to split all exception types into «runtime errors» and «logic errors» and let you proceed from there with your own exception types. There are, of course, alternative ways to structure that hierarchy, which might be more appropriate in your design.

Update: Portability Linux vs Windows

As Loki Astari and unixman83 noted in their answer and comments below, the constructor of the exception class does not take any arguments according to C++ standard. Microsoft C++ has a constructor taking arguments in the exception class, but this is not standard. The runtime_error class has a constructor taking arguments (char*) on both platforms, Windows and Linux. To be portable, better use runtime_error.

(And remember, just because a specification of your project says your code does not have to run on Linux, it does not mean it does never have to run on Linux.)

  1. 1. Инварианты
  2. 2. Виды исключений
  3. 3. std::logic_error
  4. 4. std::invalid_argument
  5. 5. std::domain_error
  6. 6. std::length_error
  7. 7. std::out_of_range
  8. 8. std::future_error
  9. 9. std::runtime_error
  10. 10. std::range_error
  11. 11. std::overflow_error
  12. 12. std::underflow_error
  13. 13. std::system_error
  14. 14. std::ios_base::failure
  15. 15. std::bad_typeid
  16. 16. std::bad_cast
  17. 17. std::bad_weak_ptr
  18. 18. std::bad_function_call
  19. 19. std::bad_alloc
  20. 20. std::bad_array_new_length
  21. 21. std::bad_exception

Что такое исключение? Это ситуация, которая не предусмотрена стандартным поведением программы. Например, попытка доступа к элементу в классе Vector (который мы разбирали в статье про

классы

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

  • Как правило в таких случаях, автор класса

    Vector

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

    Vector

    не может всегда контролировать правильность работы этого класса, поэтому ему нужно сообщить о том, что что-то пошло не так.

Для разрешения таких ситуация в C++ можно использовать технику исключений.


Рассмотрим, как написать вызов исключения в случае попытки доступа к элементу по индексу, который не существует в классе Vector.

double& Vector::operator[](int i)
{
    if (i<0 || size()<=i) throw out_of_range{"Vector::operator[]"};
    return elem[i];
}

Здесь применяется исключение

out_of_range.

Данное исключение определено в заголовочном файле

.

Оператор

throw

передаёт контроль обработчику для исключений типа

out_of_range

в некоторой функции, которая прямо или косвенно вызывает

Vector::operator

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

try catch.

void f(Vector& v)
{
    // ...
    try { // блок обработки функции с исключением
        v[v.size()] = 7; // попытка доступа к элементу за пределами вектора
    }
    catch (out_of_range) { // ловим ошибку out_of_range 
        // ... обработки ошибки out_of_range ...
    }
    // ...
}

Инварианты

Также блоки

try catch

позволяют производить обработку нескольких различных исключений, что вносит инвариантность в работу механизма исключений C++.

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

Vector::Vector(int s)
{
    if (s < 0) throw length_error{};
    elem = new double[s];
    sz = s;
}

Данный конструктор может выбросить исключение в двух случаях:

  • Если в качестве аргумента

    size

    будет передано отрицательное значение
  • Если оператор

    new

    не сможет выделить память


length_error

— это стандартный оператор исключений, поскольку библиотека std часто использует данные исключения при своей работе.

Обработка исключений будет выглядеть следующим образом:

void test()
{
    try {
        Vector v(−27);
    }
    catch (std::length_error) {
        // обработка отрицательного размера вектора
    }
    catch (std::bad_alloc) {
        // обработка ошибки выделения памяти
    }
}

Также можно выделить свои собственные исключения.

Виды исключений

Все исключения стандартной библиотеки наследуются от

std::exception.

На данный момент существуют следующие виды исключений:

  • logic_error
  • invalid_argument
  • domain_error
  • length_error
  • out_of_range
  • future_error (C++11)
  • runtime_error
  • range_error
  • overflow_error
  • underflow_error
  • system_error (C++11)
  • ios_base::failure (начиная с C++11)
  • bad_typeid
  • bad_cast
  • bad_weak_ptr (C++11)
  • bad_function_call (C++11)
  • bad_alloc
  • bad_array_new_length (C++11)
  • bad_exception
  • ios_base::failure (до C++11)

std::logic_error

Исключение определено в заголовочном файле

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

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

std::invalid_argument

Исключение определено в заголовочном файле

Наследован от std::logic_error. Определяет исключение, которое должно быть брошено в случае неправильного аргумента.

Например, на MSDN приведён пример, когда в объект класса bitset из стандартной библиотеки

// invalid_arg.cpp  
// compile with: /EHsc /GR  
#include <bitset>  
#include <iostream>  

using namespace std;  

int main( )  
{  
   try   
   {  
      bitset< 32 > bitset( string( "11001010101100001b100101010110000") );  
   }  
   catch ( exception &e )   
   {  
      cerr << "Caught " << e.what( ) << endl;  
      cerr << "Type " << typeid( e ).name( ) << endl;  
   };  
}  
* Output:   
Caught invalid bitset<N> char  
Type class std::invalid_argument  
* 

В данном примере передаётся неправильная строка, внутри которой имеется символ ‘b’, который будет ошибочным.

std::domain_error

Исключение определено в заголовочном файле

Наследован от std::logic_error. Определяет исключение, которое должно быть брошено в случае если математическая функция не определена для того аргумента, который ей передаётся, например:

std::sqrt(-1)

std::length_error

Исключение определено в заголовочном файле

Наследован от std::logic_error. Определяет исключение, которое должно быть броше в том случае, когда осуществляется попытка реализации превышения допустим пределов для объекта. Как это было показано для размера вектора в начале статьи.

std::out_of_range

Исключение определено в заголовочном файле

Наследован от std::logic_error. Определяет исключение, которое должно быть брошено в том случае, когда происходит выход за пределы допустимого диапазона значений объекта. Как это было показано для диапазона значений ветора в начале статьи.

std::future_error

Исключение определено в заголовочном файле

Наследован от std::logic_error. Данное исключение может быть выброшено в том случае, если не удалось выполнить функцию, которая работает в асинхронном режиме и зависит от библиотеки потоков. Это исключение несет код ошибки совместимый с

std::error_code

.

std::runtime_error

Исключение определено в заголовочном файле

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

std::range_error

Исключение определено в заголовочном файле

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

underflow_error

или

overflow_error

.

std::overflow_error

Исключение определено в заголовочном файле

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

std::underflow_error

Исключение определено в заголовочном файле

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

std::system_error

Исключение определено в заголовочном файле


std::system_error

— это тип исключения, которое вызывается различными функциями стандартной библиотеки (как правило, функции, которые взаимодействуют с операционной системой, например, конструктор

std::thread

), при этом исключение имеет соответствующий

std::error_code

.

std::ios_base::failure

Исключение определено в заголовочном файле

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

std::bad_typeid

Исключение определено в заголовочном файле

Исключение этого типа возникает, когда оператор

typeid

применяется к нулевому указателю полиморфного типа.

#include <iostream>
#include <typeinfo>

struct S { // Тип должен быть полиморфным
    virtual void f();
}; 

int main()
{
    S* p = nullptr;
    try {
        std::cout << typeid(*p).name() << 'n';
    } catch(const std::bad_typeid& e) {
        std::cout << e.what() << 'n';
    }
}

std::bad_cast

Исключение определено в заголовочном файле

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

#include <iostream>
#include <typeinfo>

struct Foo { virtual ~Foo() {} };
struct Bar { virtual ~Bar() {} };

int main()
{
    Bar b;
    try {
        Foo& f = dynamic_cast<Foo&>(b);
    } catch(const std::bad_cast& e)
    {
        std::cout << e.what() << 'n';
    }
}

std::bad_weak_ptr

Исключение определено в заголовочном файле


std::bad_weak_ptr

– тип объекта, генерируемый в качестве исключения конструкторами

std::shared_ptr

, которые принимают

std::weak_ptr

в качестве аргумента, когда

std::weak_ptr

ссылается на уже удаленный объект.

#include <memory>
#include <iostream>
int main()
{
    std::shared_ptr<int> p1(new int(42));
    std::weak_ptr<int> wp(p1);
    p1.reset();
    try {
        std::shared_ptr<int> p2(wp);
    } catch(const std::bad_weak_ptr& e) {
        std::cout << e.what() << 'n';
    }
}

std::bad_function_call

Исключение определено в заголовочном файле

Данное исключение генерируется в том случае, если был вызван метод

std::function::operator()

объекта

std::function

, который не получил объекта функции, то есть ему был передан в качестве инициализатора nullptr, например, а объект функции так и не был передан.

#include <iostream>
#include <functional>

int main()
{
    std::function<int()> f = nullptr;
    try {
        f();
    } catch(const std::bad_function_call& e) {
        std::cout << e.what() << 'n';
    }
}

std::bad_alloc

Исключение определено в заголовочном файле

Вызывается в том случае, когда не удаётся выделить память.

std::bad_array_new_length

Исключение определено в заголовочном файле

Исключение вызывается в следующих случаях:

  1. Массив имеет отрицательный размер
  2. Общий размер нового массива превысил максимальное значение, определяемое реализацией
  3. Количество элементов инициализации превышает предлагаемое количество инициализирующих элементов
#include <iostream>
#include <new>
#include <climits>

int main()
{
    int negative = -1;
    int small = 1;
    int large = INT_MAX;
    try {
        new int[negative];           // negative size
        new int[small]{1,2,3};       // too many initializers
        new int[large][1000000];     // too large
    } catch(const std::bad_array_new_length &e) {
        std::cout << e.what() << 'n';
    }
}

std::bad_exception

Исключение определено в заголовочном файле


std::bad_exception

— это тип исключения в C++, которое выполняется в следующих ситуациях:

  1. Если нарушается динамическая спецификация исключений
  2. Если

    std::exception_ptr

    хранит копию пойманного исключения, и если конструктор копирования объекта исключения поймал current_exception, тогда генерируется исключение захваченных исключений.
#include <iostream>
#include <exception>
#include <stdexcept>

void my_unexp() { throw; }

void test() throw(std::bad_exception)
{
    throw std::runtime_error("test");
}

int main()
{
    std::set_unexpected(my_unexp);
    try {
         test();
    } catch(const std::bad_exception& e)
    {
        std::cerr << "Caught " << e.what() << 'n';
    }
}

Introduction

.NET provides a structured way of handling the runtime errors. Business logic and error handling code is separate. The errors that occur during the execution of a program are called the runtime errors or the exceptions. Some of the examples of runtime erros are Division by Zero, Stack overflow, Invalid type casting, and File not found.

Object-Oriented way of error handling is,

  1. Classes to handle different types of errors.
  2. FileNotFoundException class to represent, if the file not found.
  3. IOException class to represent, if the file is already open.
  4. SecurityException class to represent, if the caller does not have permission to access the file.
  5. Easy to program and extend

Runtime errors occur during the execution of a program. Some exceptions are given below,

  • Division by 0 exception occurs, when the denominator is zero.
  • Stack overflow exception occurs, when there is no memory available to allocate on stack.
  • File not found exception can occur, when we try to access a file from a particular location, where it does not exit.

These exceptions abnormally terminate a program. To avoid this, C# provides an in-built feature that is called an exception handling mechanism.

Exception handling mechanism provides a way to respond to the run time errors in the program by transferring control to special code called handler. This feature allows a clean separation between error detection code or business logic and error handling code.

The exception handling mechanism in C# provides try and catch blocks to effectively handle it.

Let’s see how to handle an exception practically.

  1. For this, create new C# console project, as shown below.

    Handling Runtime Exceptions In C#

  2. Let’s write a code for reading a text file for the specific path, as shown below.
  • The file path is shown below.

    Handling Runtime Exceptions In C#

  • Now, let’s try to read this sample file, using C# code, as given below.
    1. using System;  
    2. using System.IO;  
    3. namespace ConsoleApplication1 {  
    4.     class Program {  
    5.         static void Main(string[] args) {  
    6.             StreamReader strmRdr = new StreamReader(@ «E:Exception Handlingsample.txt»);  
    7.             Console.WriteLine(strmRdr.ReadToEnd());   
    8.             strmRdr.Close();   
    9.             Console.ReadLine();  
    10.         }  
    11.     }  
    12. }  

    Handling Runtime Exceptions In C#

  1. Let’s implement one exception to this reading code. Let’s change the file name so that in the file path, we can make it not exist and change the code, as shown below, and see what happens.

    Handling Runtime Exceptions In C#

    As you see here, you will be getting an Exception System.IO.FileNotFoundException.

  1. Now, we will see how to handle this. Using try catch, rewrite the code, as shown below.
    1. try {  
    2.     StreamReader strmRdr = new StreamReader(@ «E:Exception Handlingsample1.txt»);  
    3.     Console.WriteLine(strmRdr.ReadToEnd());   
    4.     strmRdr.Close();   
    5.     Console.ReadLine();  
    6. catch (Exception ex) {  
    7.     Console.WriteLine(ex.Message);  
    8. }   

    Handling Runtime Exceptions In C#

    Here, we will get the exact exception and the application will not crash.

    It is not a good way because here, we are displaying all the error messages which sometimes the user can’t understand. The hackers may use this information hack.

    Thus, we need to hide all these things and we have to display an error message in a better way. Exception is actually a class, which is derived from System.Exception class. This class has several useful properties, which provide valuable information about the exception.

    There are two main methods in this exception.

    Message
    Gets message that describes the current exception.

    Stack Trace
    Provides the call stack to the line number in the method, where the exception occurred

  1. For this exception, we can use FileNotFoundException in catch block, so whenever this exception gets this, catch block will execute , else it does not, as shown below.

    Handling Runtime Exceptions In C#

    See above that whenever we get FileNotFoundException, we can give the user a defined messge. Let’s check if an exception is something else other than this, then it will happen.

    Handling Runtime Exceptions In C#

    I have changed the directory name, which does not exist, so the new exception is there. Since I am handling only FileNotFound exception, we have to give DirectoryNot found exception; you have to show some message and block this error.

    Handling Runtime Exceptions In C#

  1. Now, we have to know how to handle these exceptions but it still has a problem; i.e., I am getting an error in the line.
    1. StreamReader strmRdr = new StreamReader(@«C:Exception Handlingsample1.txt»);  

    Whatever is there will not execute. After getting an error, it goes to catch block, then this stream reader is still open. We need to release this resource for which we have to write final block after catch blocks. Actually, it is a good practice to write final block after catch block, if we are using resources like stramreader conection open etc., as shown below. 

    1. using System;  
    2. using System.IO;  
    3. namespace ConsoleApplication1 {  
    4.     class Program {  
    5.         static void Main(string[] args) {  
    6.             StreamReader strmRdr = null;  
    7.             try {  
    8.                 strmRdr = new StreamReader(@ «C:Exception Handlingsample.txt»);  
    9.                 Console.WriteLine(strmRdr.ReadToEnd());   
    10.                 strmRdr.Close();   
    11.                 Console.ReadLine();  
    12.             } catch (FileNotFoundException fx) {  
    13.                 Console.WriteLine(«Please check your file Name»);  
    14.                 Console.ReadLine();  
    15.             } catch (DirectoryNotFoundException fx) {  
    16.                 Console.WriteLine(«Please give a path where file exactly locate»);  
    17.                 Console.ReadLine();  
    18.             } finally {  
    19.                 if (strmRdr != null) {  
    20.                     strmRdr.Close();  
    21.                 }  
    22.             }  
    23.         }  
    24.     }   

    The Finally block will execute every time, so make sure whatever code is there inside the finally block is perfect.

  1. If you get an exception inside the finally block it looks like this.

    Handling Runtime Exceptions In C#

As you can see the file path is correct, even though the finally block is executed. I got an error conversion failed exception. Thus, what to do in these situations?

We can handle this in higher levels, as shown below, by adding try catch for all these codes

Handling Runtime Exceptions In C#

Other .NET exception classes are given below.

  • SystemException
  • FornatException
  • ArithmaticException
  • CoreException
  • ArgumentException
  • OutOfMemoryException
  • NullReferenceException
  • InvalidOperationException
  • FormatException
  • ArrayTypeMismatchException
  • NotSupportedException, etc.

Note

  • Exception class is the base class for all the exceptions
  • Any code that is written cannot be completely bug-free.
  • It is the responsibility of the developer to see to it that there are minimum bugs in the code.
Определено в заголовке <stdexcept>
class runtime_error;

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

Исключения типа std::runtime_error генерируются следующими компонентами стандартной библиотеки: std::locale::locale и std::locale::combine .

Кроме того, следующие стандартные типы исключений являются производными от std::runtime_error :

  • std::range_error
  • std::overflow_error
  • std::underflow_error
  • std::chrono::ambiguous_local_time
  • std::chrono::nonexistent_local_time
  • std::format_error
(since C++20)

Member functions

(constructor)

создает новый объект runtime_error с заданным сообщением
(функция публичного члена)

operator=

заменяет объект runtime_error
(функция публичного члена)

what

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

std::runtime_error::runtime_error

runtime_error( const std::string& what_arg );
(1)
runtime_error( const char* what_arg );
(2) (since C++11)
(3)
runtime_error( const runtime_error& other );
(until C++11)
runtime_error( const runtime_error& other ) noexcept;
(since C++11)

1-2) Создает объект исключения с what_arg в качестве пояснительной строки, к которой можно получить доступ через what() .

3) Конструктор копирования. Если *this и other имеют динамический тип std::runtime_error , то std::strcmp(what(), other.what()) == 0 (начиная с C++11).

Parameters

what_arg explanatory string
other другой объект исключения для копирования

Exceptions

Notes

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

std::runtime_error::operator=

runtime_error& operator=( const runtime_error& other );
(until C++11)
runtime_error& operator=( const runtime_error& other ) noexcept;
(since C++11)

Сопоставляет содержимое с содержимым other . Если *this и other имеют динамический тип std::runtime_error , то std::strcmp(what(), other.what()) == 0 после присваивания (начиная с C++11).

Parameters

other другой объект исключения для присвоения

Return value

*this.

std::runtime_error::what

virtual const char* what() const throw();
(until C++11)
virtual const char* what() const noexcept;
(since C++11)

Возвращает пояснительную строку.

Parameters

(none).

Return value

Указатель на завершающуюся нулем строку с пояснительной информацией. Строка подходит для преобразования и отображения в виде std::wstring . Указатель гарантированно действителен, по крайней мере, до тех пор, пока объект исключения, из которого он получен, не будет уничтожен, или пока не будет вызвана неконстантная функция-член (например, оператор присваивания копии) для объекта исключения.

Notes

Реализации разрешены, но не обязаны переопределять what() .

Наследуется от std :: exception

Member functions

[virtual]

уничтожает объект исключения
(виртуальная публичная функция-член std::exception )

[virtual]

возвращает пояснительную строку
(виртуальная публичная функция-член std::exception )


C++

  • std::rethrow_exception

    Выбрасывает ранее захваченный объект исключения,на который ссылается указатель или копия этого указателя Не определено,была ли сделана копия.

  • std::rethrow_if_nested

    Если E не является полиморфным типом класса,то std::nested_exception недоступная неоднозначная база этого эффекта.

  • std::set_terminate

    Убедитесь,что новая функция глобального обработчика завершения возвращает ранее установленный std::terminate_handler.

  • std::set_unexpected

    Делает f новый глобальный std::unexpected_handler,возвращающий ранее установленную функцию,потокобезопасным.

Понравилась статья? Поделить с друзьями:

Читайте также:

  • Исключение ip камеры hikvision как исправить
  • Искаженное ошибочное восприятие реального объекта это
  • Искаженное ошибочное восприятие реально существующего объекта носящее устойчивый характер
  • Искаженное или ошибочное восприятие это
  • Искажение экрана на компьютере как исправить

  • 0 0 голоса
    Рейтинг статьи
    Подписаться
    Уведомить о
    guest

    0 комментариев
    Старые
    Новые Популярные
    Межтекстовые Отзывы
    Посмотреть все комментарии