Evgenii Legotckoi
16 октября 2017 г. 3:31

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

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

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

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


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

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

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

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

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

Инварианты

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

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

  1. Vector::Vector(int s)
  2. {
  3. if (s < 0) throw length_error{};
  4. elem = new double[s];
  5. sz = s;
  6. }

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

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

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

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

  1. void test()
  2. {
  3. try {
  4. Vector v(−27);
  5. }
  6. catch (std::length_error) {
  7. // обработка отрицательного размера вектора
  8. }
  9. catch (std::bad_alloc) {
  10. // обработка ошибки выделения памяти
  11. }
  12. }

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

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

Все исключения стандартной библиотеки наследуются от 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 из стандартной библиотеки

  1. // invalid_arg.cpp
  2. // compile with: /EHsc /GR
  3. #include <bitset>
  4. #include <iostream>
  5.  
  6. using namespace std;
  7.  
  8. int main( )
  9. {
  10. try
  11. {
  12. bitset< 32 > bitset( string( "11001010101100001b100101010110000") );
  13. }
  14. catch ( exception &e )
  15. {
  16. cerr << "Caught " << e.what( ) << endl;
  17. cerr << "Type " << typeid( e ).name( ) << endl;
  18. };
  19. }
  20. \* Output:
  21. Caught invalid bitset<N> char
  22. Type class std::invalid_argument
  23. *\

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

std::domain_error

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

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

  1. 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 применяется к нулевому указателю полиморфного типа.

  1. #include <iostream>
  2. #include <typeinfo>
  3.  
  4. struct S { // Тип должен быть полиморфным
  5. virtual void f();
  6. };
  7.  
  8. int main()
  9. {
  10. S* p = nullptr;
  11. try {
  12. std::cout << typeid(*p).name() << '\n';
  13. } catch(const std::bad_typeid& e) {
  14. std::cout << e.what() << '\n';
  15. }
  16. }

std::bad_cast

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

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

  1. #include <iostream>
  2. #include <typeinfo>
  3.  
  4. struct Foo { virtual ~Foo() {} };
  5. struct Bar { virtual ~Bar() {} };
  6.  
  7. int main()
  8. {
  9. Bar b;
  10. try {
  11. Foo& f = dynamic_cast<Foo&>(b);
  12. } catch(const std::bad_cast& e)
  13. {
  14. std::cout << e.what() << '\n';
  15. }
  16. }

std::bad_weak_ptr

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

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

  1. #include <memory>
  2. #include <iostream>
  3. int main()
  4. {
  5. std::shared_ptr<int> p1(new int(42));
  6. std::weak_ptr<int> wp(p1);
  7. p1.reset();
  8. try {
  9. std::shared_ptr<int> p2(wp);
  10. } catch(const std::bad_weak_ptr& e) {
  11. std::cout << e.what() << '\n';
  12. }
  13. }

std::bad_function_call

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

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

  1. #include <iostream>
  2. #include <functional>
  3.  
  4. int main()
  5. {
  6. std::function<int()> f = nullptr;
  7. try {
  8. f();
  9. } catch(const std::bad_function_call& e) {
  10. std::cout << e.what() << '\n';
  11. }
  12. }

std::bad_alloc

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

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

std::bad_array_new_length

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

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

  1. Массив имеет отрицательный размер
  2. Общий размер нового массива превысил максимальное значение, определяемое реализацией
  3. Количество элементов инициализации превышает предлагаемое количество инициализирующих элементов
  1. #include <iostream>
  2. #include <new>
  3. #include <climits>
  4.  
  5. int main()
  6. {
  7. int negative = -1;
  8. int small = 1;
  9. int large = INT_MAX;
  10. try {
  11. new int[negative]; // negative size
  12. new int[small]{1,2,3}; // too many initializers
  13. new int[large][1000000]; // too large
  14. } catch(const std::bad_array_new_length &e) {
  15. std::cout << e.what() << '\n';
  16. }
  17. }

std::bad_exception

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

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

  1. Если нарушается динамическая спецификация исключений
  2. Если std::exception_ptr хранит копию пойманного исключения, и если конструктор копирования объекта исключения поймал current_exception, тогда генерируется исключение захваченных исключений.
  1. #include <iostream>
  2. #include <exception>
  3. #include <stdexcept>
  4.  
  5. void my_unexp() { throw; }
  6.  
  7. void test() throw(std::bad_exception)
  8. {
  9. throw std::runtime_error("test");
  10. }
  11.  
  12. int main()
  13. {
  14. std::set_unexpected(my_unexp);
  15. try {
  16. test();
  17. } catch(const std::bad_exception& e)
  18. {
  19. std::cerr << "Caught " << e.what() << '\n';
  20. }
  21. }

Вам это нравится? Поделитесь в социальных сетях!

P
  • 16 октября 2017 г. 9:00

std::underflow_error - это не "число имеет слишком большое отрицательное значение", а потеря точности при вычислениях, т.е. результат настолько мал, что не может быть представлен числом в формате IEEE754

Evgenii Legotckoi
  • 16 октября 2017 г. 12:46

Спасибо. Дополнил.

Комментарии

Только авторизованные пользователи могут публиковать комментарии.
Пожалуйста, авторизуйтесь или зарегистрируйтесь