Evgenii Legotckoi
16 жовтня 2017 р. 03: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.

На даний момент існують такі види винятків:

  • логічна_помилка
  • недійсний_аргумент
  • помилка домену
  • довжина_помилка
  • поза_діапазоном
  • future_error (C++11)
  • Помилка виконання
  • помилка_діапазону
  • помилка переповнення
  • underflow_error
  • системна_помилка (C++11)
  • ios_base::failure (починаючи з C++11)
  • bad_typeid
  • bad_cast
  • bad_weak_ptr (C++11)
  • поганий_виклик_функції (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 р. 09:00

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

Evgenii Legotckoi
  • 16 жовтня 2017 р. 12:46

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

Коментарі

Only authorized users can post comments.
Please, Log in or Sign up
  • Останні коментарі
  • Evgenii Legotckoi
    16 квітня 2025 р. 17:08
    Благодарю за отзыв. И вам желаю всяческих успехов!
  • IscanderChe
    12 квітня 2025 р. 17:12
    Добрый день. Спасибо Вам за этот проект и отдельно за ответы на форуме, которые мне очень помогли в некоммерческих пет-проектах. Профессиональным программистом я так и не стал, но узнал мно…
  • AK
    01 квітня 2025 р. 11:41
    Добрый день. В данный момент работаю над проектом, где необходимо выводить звук из программы в определенное аудиоустройство (колонки, наушники, виртуальный кабель и т.д). Пишу на Qt5.12.12 поско…
  • Evgenii Legotckoi
    09 березня 2025 р. 21:02
    К сожалению, я этого подсказать не могу, поскольку у меня нет необходимости в обходе блокировок и т.д. Поэтому я и не задавался решением этой проблемы. Ну выглядит так, что вам действитель…
  • VP
    09 березня 2025 р. 16:14
    Здравствуйте! Я устанавливал Qt6 из исходников а также Qt Creator по отдельности. Все компоненты, связанные с разработкой для Android, установлены. Кроме одного... Когда пытаюсь скомпилиров…