Рубріки: Теория

Как обрабатывать исключения в С++: что такое throw, try и catch

Светлана Лазутина

При выполнении кода на C++ могут возникать разные ошибки, которые не позволяют программе выполнять свою работу. Для работы с ошибками или исключениями в C++ используются ключевые слова try , catch и throw.

Содержание статьи:

1. Вступление: виды исключений и знакомство с try, catch и throw в C++

2. Генерируем исключения в C++

3. Ищем ошибки в коде

4. Обрабатываем ошибки с try и catch in С++

5. Как работают throw, try и catch в C++: примеры

6. Еще немного о порядке обработке ошибок в C++

7. Подводим итоги

Вступление: виды исключений и знакомство с try, catch и throw в C++

Есть два вида исключений, с которыми вы можете столкнуться в процессе:

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

Когда происходит какое-то событие, прерывающее нормальное функционирование программы, C ++ обычно останавливается и выдает сообщение об ошибке. Когда это происходит, говорят, что C++ выбрасывает ошибку —  throw an exception. Мы уже упоминали, что для работы с ошибками или исключениями в C++ используются определенные ключевые слова, давайте познакомимся с ними поближе:

  • try: позволяет определить блок кода, который будет проверяться на наличие ошибок во время его выполнения;
  • throw: нужен для создания и отображения исключений и используется для перечисления ошибок, которые генерирует функция, но не может самостоятельно обрабатывать исключения;
  • catchблок кода, который выполняется при возникновении определенного исключения в блоке try.

Давайте посмотрим, как выглядит пример кода в С++ с использованием try catch и throw:

try {
  int age = 15;
  if (age >= 18) {
    cout << "Access granted - you are old enough.";
  } else {
    throw (age);
  }
}
catch (int myNum) {
  cout << "Access denied - You must be at least 18 years old.\n";
  cout << "Age is: " << myNum;
}

Вкратце объясним, как работают операторы try и catch в С++ на примере этого блока. Мы используем блок try для тестирования определенных строк кода: если переменная age меньше 18, мы генерируем исключение и обрабатываем его в блоке catch.

С помощью catch мы перехватываем ошибку и прописываем способ ее обработки. Оператор принимает параметр: в примере используется переменная типа int myNum для вывода значения возраста. 

Если все данные соответствуют установленным параметрам, то ошибки не возникает. Например, если указанный возраст будет больше 18, а не 15, как указано в примере, то блок catch просто пропускается.

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

Access denied - You must be at least 18 years old.
Age is: 15

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

Access denied - You must be at least 18 years old.
Error number: 505

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

Генерируем исключения в C++

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

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

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

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

Здесь возникают основные проблемы с обработкой ошибок при использовании оператора if:

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

Вот так выглядит обработка ошибок в коде при использовании оператора if:

unsigned int error_type = 0;

int add(int a, int b)
{
    if (a > 100 || b > 100)
    {
        error_type = 1;
        return -1;
    }
    else if (a < 0 || b < 0)
    {
        error_type = 2;
        return -1;
    }

    return a + b;
}

int add_wrapper(int a, int b)
{
    return add(a, b);
}

int main(int, char**) 
{
    if (add_wrapper(-1, 8) < 0)
    {
        if (error_type == 1)
        {
            std::cout << "add operation failed. parameters must be <= 100" << "\n";
        }
        else
        {
            std::cout << "add operation failed. parameters must be >= 0" << "\n";
        }
    }
    else
    {
        std::cout << "add operation succeeded" << "\n";
    }

    return 0;
}

А вот так будет выглядеть код с использованием try и catch в С++ (example):

#include <iostream>
using namespace std;
 
class Test {
public:
    Test() { cout << "Constructor of Test " << endl; }
    ~Test() { cout << "Destructor of Test " << endl; }
};
 
int main()
{
    try {
        Test t1;
        throw 10;
    }
    catch (int i) {
        cout << "Caught " << i << endl;
    }
}

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

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

Поэтому, запуская код в С++ Builder, лучше искать исключения с помощью try, catch и throw. Это сделает ваш код проще, чище и с меньшей вероятностью вы допустите ошибки в программе. 

Ищем ошибки в коде

Для того чтобы проверить блок кода на ошибки и аномалии, используется оператор try.  Так мы можем быть уверены, что если появится исключение в этой части кода, то try его заметит. Главная особенность оператора в том, что в отличие от  if / else, которые смешиваются с обычным потоком данных, try отделяет обработку ошибок от обычного течения программы. 

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

try {
   // protected code
} catch( ExceptionName e1 ) {
   // catch block
} catch( ExceptionName e2 ) {
   // catch block
} catch( ExceptionName eN ) {
   // catch block
}

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

Обрабатываем ошибки с try и catch in С++

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

try {
   // protected code
} catch( ExceptionName e ) {
  // code to handle ExceptionName exception
}

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

try {
   // protected code
} catch(...) {
  // code to handle any exception
}

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

#include <iostream>
using namespace std;

double division(int a, int b) {
   if( b == 0 ) {
      throw "Division by zero condition!";
   }
   return (a/b);
}

int main () {
   int x = 50;
   int y = 0;
   double z = 0;
 
   try {
      z = division(x, y);
      cout << z << endl;
   } catch (const char* msg) {
     cerr << msg << endl;
   }

   return 0;
}

Так как программа вызывает тип исключения const char *, в блоке catch необходимо указать const char *, чтобы ошибку можно было определить и обработать. Если скомпилировать и запустить этот блок кода, то в результате получим условие прописанное в throw:

Division by zero condition!

Как работают throw, try и catch в C++: примеры

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

#include <iostream>
using namespace std;
 
int main()
{
   int x = -1;
 
   // Some code
   cout << "Before try \n";
   try {
      cout << "Inside try \n";
      if (x < 0)
      {
         throw x;
         cout << "After throw (Never executed) \n";
      }
   }
   catch (int x ) {
      cout << "Exception Caught \n";
   }
 
   cout << "After catch (Will be executed) \n";
   return 0;
}

В результате получается следующая последовательность:

Before try
Inside try
Exception Caught
After catch (Will be executed)

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

#include <iostream>
using namespace std;
 
int main()
{
    try  {
       throw 'a';
    }
    catch (int x)  {
        cout << "Caught ";
    }
    return 0;
}

В этом примере кода исключение является символом, но блок catch для захвата символа отсутствует. В результате блок вернет нам не исключение, а вот такое предупреждение:

terminate called after throwing an instance of 'char'
 
This application has requested the Runtime to terminate it in an 
unusual way. Please contact the application's support team for 
more information.

С помощью try / catch можно указывать кастомные типы исключений, наследуя и переопределяя функциональность класса исключений. В примере ниже приведем код, который покажет, как вы можете использовать класс std :: exception для генерации собственной ошибки стандартным способом:

#include <iostream>
#include <exception>
using namespace std;

struct MyException : public exception {
   const char * what () const throw () {
      return "C++ Exception";
   }
};
 
int main() {
   try {
      throw MyException();
   } catch(MyException& e) {
      std::cout << "MyException caught" << std::endl;
      std::cout << e.what() << std::endl;
   } catch(std::exception& e) {
      //Other errors
   }
}

Результат выполнения кода выглядит так: 

MyException caught
C++ Exception

Еще немного о порядке обработке ошибок в C++

Когда мы прописываем операторы try / catch в коде, то исключение выбрасывается только при исполнении определенных условий. Рассмотрим как работают try, catch и throw в С++ на примере:

#include <cmath> // for sqrt() function
#include <iostream>

int main()
{
    std::cout << "Enter a number: ";
    double x {};
    std::cin >> x;

    try // Ищет исключения в блоке и направляет их к обработчику catch 
    {
        // этот блок сработает, если пользователь ввел отрицательное число
        if (x < 0.0)
            throw "Can not take sqrt of negative number"; // throw выбрасывает исключение типа const char*

        // Если пользователь ввел число больше 0, то выполняется этот блок кода
        std::cout << "The sqrt of " << x << " is " << std::sqrt(x) << '\n';
    }
    catch (const char* exception) // обработчик исключений типа const char*
    {
        std::cerr << "Error: " << exception << '\n';
    }
}

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

Enter a number: 49
The sqrt of 49 is 7

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

Enter a number: -4
Error: Can not take sqrt of negative number

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

Подводим итоги

Как использовать try, catch и throw в С++, мы разобрались. Теперь кратко напомним, зачем все это нужно:

  • В первую очередь, код с try / catch занимает меньше строк и легче читается. Блоков с операторами if / else может быть очень много и они будут повторяться, тогда как try / catch содержит только два блока.
  • Исключения C++ заставляют код определять условия ошибки и обрабатывать исключения. Это позволяет не останавливать выполнение программы.
  • После обнаружения исключения код на C++ перестает считывать объекты в блоке кода, сокращая использование программных ресурсов.
  • Получение понятного сообщения об ошибке сильно упрощает процесс исправления бага. Особенно это полезно в случае, если исключение выбрасывается не из-за написанного кода, а, к примеру, из-за использованной библиотеки.
  • Типы ошибок в C++ можно группировать вместе, что позволяет создавать иерархию объектов исключений, группировать их по именам, классам и категоризировать по виду.

Видео: С++ try catch. Обработка исключений С++. Try catch: что это. Изучение С++ для начинающих

Останні статті

Обучение Power BI – какие онлайн курсы аналитики выбрать

Сегодня мы поговорим о том, как выбрать лучшие курсы Power BI в Украине, особенно для…

13.01.2024

Work.ua назвал самые конкурентные вакансии в IТ за 2023 год

В 2023 году во всех крупнейших регионах конкуренция за вакансию выросла на 5–12%. Не исключением…

08.12.2023

Украинская IT-рекрутерка создала бесплатный трекер поиска работы

Unicorn Hunter/Talent Manager Лина Калиш создала бесплатный трекер поиска работы в Notion, систематизирующий все этапы…

07.12.2023

Mate academy отправит работников в 10-дневный оплачиваемый отпуск

Edtech-стартап Mate academy принял решение отправить своих работников в десятидневный отпуск – с 25 декабря…

07.12.2023

Переписки, фото, история браузера: киевский программист зарабатывал на шпионаже

Служба безопасности Украины задержала в Киеве 46-летнего программиста, который за деньги устанавливал шпионские программы и…

07.12.2023

Как вырасти до сеньйора? Девелопер создал популярную подборку на Github

IT-специалист Джордан Катлер создал и выложил на Github подборку разнообразных ресурсов, которые помогут достичь уровня…

07.12.2023