C++. Поняття виключної ситуації. Блок try…catch. Оператор throw. Приклади використання

Поняття виключної ситуації. Блок try…catch. Оператор throw. Приклади використання


Зміст


1. Типи помилок, які можуть виникати в програмах

У програмах на C++ можуть виникати помилки. Розрізняють три типи помилок, які можуть виникати у програмах:

  • синтаксичні. Це помилки в синтаксисі мови C++. Вони можуть зустрічатись в іменах операторів, функцій, розділювачів і т.д. У цьому випадку компілятор визначає наявність синтаксичної помилки і видає відповідне повідомлення. У результаті виконавчий (*.exe) файл не створюється і програма не виконується;
  • логічні. Це помилки програміста, які важко виявити на етапі розробки програми. Ці помилки виявляються на етапі виконання під час тестування роботи програми. Логічні помилки можна виявити тільки за результатами роботи програми. Прикладом логічних помилок може бути неправильна робота з покажчиками у випадках виділення/звільнення пам’яті;
  • помилки часу виконання. Такі помилки виникають під час роботи програми. Помилки часу виконання можуть бути логічними помилками програміста, помилками зовнішніх подій (наприклад, нехватка оперативної пам’яті), невірним введенням даних користувачем тощо. У результаті виникнення помилки часу виконання, програма призупиняє свою роботу. Тому, важливо перехопити цю помилку і правильно обробити її для того, щоб програма продовжила свою роботу без зупинки.

Дана тема висвітлює застосування механізму перехоплення помилок часу виконання.

 

2. Поняття виключної ситуації

Виключна ситуація – це подія, що призвела до збою в роботі програми. У результаті виникнення виключної ситуації програма не може коректно продовжити своє виконання.

Приклади дій у програмі, що можуть призвести до виникнення виключних ситуацій:

  • ділення на нуль;
  • нехватка оперативної пам’яті при застосуванні оператора new для її виділення (або іншої функції);
  • доступ до елементу масиву за його межами (помилковий індекс);
  • переповнення значення для деякого типу;
  • взяття кореня з від’ємного числа;
  • інші ситуації.

 

3. Поняття виключення

У мові C++ виключення – це спеціальний об’єкт класу або значення базового типу, що описує (визначає) конкретну виключну ситуацію і відповідним чином обробляється.

При написанні програми система опису виключних ситуацій вибирається програмістом на власний розсуд. Можна створити свою кваліфікацію помилок, які можуть виникати у програмі. Наприклад, програміст може кваліфікувати різні типи помилок числовим (цілочисельним) значенням або побудувати власну ієрархію класів що описують виключні ситуації. Крім того, можна використовувати можливості класів C++, які є похідними від класу exception.

 

4. Засоби мови C++ для обробки виключних ситуацій. Загальна форма конструкції try…catch. Призначення

Мова програмування C++ дає можливість перехоплювати виключні ситуації та відповідним чином їх обробляти.

Механізм перехоплення виключень C++ дозволяє генерувати виключення у тому місці, в якому воно виникає – це є дуже зручно. Не потрібно “видумувати” власні способи обробки виключень, які виникли у функціях нижчого рівня, для того щоб передати їх у функції вищого рівня.

Для перехоплення та обробки виключних ситуацій у мові C++ введено конструкцію try…catch, яка має наступну загальну форму:

try {
    // тіло блоку try
    // ...
    // генерування виключення оператором throw
}
catch(type1 argument1)
{
    // тіло блоку catch
}
catch(type2 argument2)
{
    // тіло блоку catch
}
...
catch(typeN argumentN)
{
    // тіло блоку catch
}

де

  • type1, type2, …, typeN – відповідно тип аргументів argument1, argument2, …, argumentN.

Код, який потрібно проконтролювати, повинен виконуватись всередниі блоку try. Виключні ситуації перехоплюються оператором catch, який слідує безпосередньо за блоком try в якому вони виникли.

У блоці try можуть бути розміщені оператори і функції. Якщо у блоці try генерується відповідна виключна ситуація, то вона перехоплюється відповідним блоком catch. Вибір того чи іншого блоку catch здійснюється в залежності від типу виключної ситуації. Після виникнення виключної ситуації певного типу, викликається блок catch з таким самим типом аргументу. Аргумент приймає деяке значення, яке відповідним чином обробляється (виводиться на екран повідомлення про помилку тощо).

Якщо у блоці try виникне виключна ситуація, яка не передбачена блоком catch, то викликається стандартна функція terminate(), яка за замовчуванням викликає функцію abort(). Ця стандартна функція припиняє виконання програми.

 

5. Оператор throw. Призначення

Щоб у блоці try згенерувати виключну ситуацію, потрібно використати оператор throw. Оператор throw може бути викликаний всередині блоку try або всередині функції, яка викликається з блоку try.

Загальна форма оператора throw наступна

throw виключення;

У результаті виконання оператора throw генерується виключення деякого типу. Це виключення повинно бути оброблене в блоці catch.

 

6. Приклади використання блоку try…catch

Приклад 1. Демонструється використання блоку try…catch для обробки виразу:

У даному виразі у трьох випадках може виникнути виключна ситуація:

  • корінь з від’ємного числа a, якщо a<0;
  • корінь з від’ємного числа b, якщо b<0;
  • ділення на 0, якщо b=0.

Тому, у блоці try…catch потрібно обробити ці три випадки.

Текст програми типу Console Application наступний:

#include <iostream>
using namespace std;

void main()
{
  // обробка виразу sqrt(a)/sqrt(b)
  double a, b;
  cout << "a = ";
  cin >> a;

  cout << "b = ";
  cin >> b;

  double c;

  try { 
    // початок блоку try
    if (b == 0)
      throw 1;
    if (b < 0)
      throw 2;
    if (a < 0)
      throw 2;

    c = sqrt(a) / sqrt(b);
    cout << "c = " << c << endl;
  }
  catch (int e) // перехоплення помилки
  {
    if (e == 1)
      cout << "Division by 0." << endl;
    if (e == 2)
      cout << "Negative root." << endl;
  }
}

Результат роботи програми

a = 5
b = 0
Division by 0.

Після застосування блоку try…catch робота програми не припиняється.

Приклад 2. Інший варіант обробки виразу з прикладу 1. Тут блок try…catch містить два оператори catch.

#include <iostream>
using namespace std;

void main()
{
  // обробка виразу sqrt(a)/sqrt(b) - варіант 2
  double a, b;
  cout << "a = ";
  cin >> a;

  cout << "b = ";
  cin >> b;

  double c;
  string s;

  try { // початок блоку try
    if (b == 0)
      throw "Division by 0.";
    if (b < 0)
      throw "Negative root.";
    if (a < 0)
      throw "Negative root.";

    // якщо виключних ситуацій немає, то продовжити обчислення
    c = sqrt(a) / sqrt(b);
    cout << "c = " << c << endl;
  }
  catch (int e) // перехоплення помилки типу int
  {
    if (e == 1)
      cout << "Division by 0." << endl;
    if (e == 2)
      cout << "Negative root." << endl;
  }
  catch (const char* e) // перехоплення помилки типу const char*
  {
    cout << e << endl;
  }
}

 



7. Приклад використання блоку try…catch у функції

Умова задачі. Написати функцію обчислення значення за заданим рядком символів, що є записом цього числа в десятковій системі числення. Передбачити випадок виходу за межі діапазону, що визначається типом int. Необхідно використати механізм виключень.

Текст програми для додатку типу Console Application наступний

// Функція обчислення значення за заданним рядком символів
int StrToInt(const char* str)
{
  char s[20];
  int t, i;
  long res = 0; // результат повернення з функції
  int len = strlen(str);

  try {
    t = 1;
    if (str[0] == '-') t = -1; // перевірка, чи перший символ '-'

    // цикл конвертування рядка в число типу int
    i = len - 1;
    while (i >= 0)
    {
      if (str[i] == '-')
      {
        if (i == 0) break; // якщо перший символ, то все добре
        else throw "Bad position of minus.";
      }

      // якщо в рядку недопустимі символи, то згенерувати виключення
      if (str[i] < '0') throw "Bad symbols";
      if (str[i] > '9') throw "Bad symbols";

      res = res + (str[i] - '0')*t;
      t *= 10;
      i--;
    }

    // якщо результат виходить за межі діапазону для 32-розрядних
    // цілочисельних значень, то згенерувати відповідне виключення
    if (res > INT32_MAX)
      throw "Out of range.";
    if (res < INT32_MIN)
      throw "Out of range.";
    return res;
  }
  catch (const char* e)
  {
    cout << e << endl;
    return 0;
  }
}

void main()
{
  int d;
  d = StrToInt("125");
  cout << "d = " << d;
}

Вищенаведена програма може бути переписана так, що блок try…catch розміщується у функції main(), як показано нижче

// Функція обчислення значення за заданним рядком символів
int StrToInt2(const char* str)
{
  char s[20];
  int t, i;
  long res = 0; // результат роботи функції
  int len = strlen(str);

  t = 1;
  if (str[0] == '-') t = -1;

  i = len - 1;
  while (i >= 0)
  {
    if (str[i] == '-')
    {
      if (i == 0) break; // якщо перший символ, то все добре
      else throw "Bad position of minus."; // інакше, згенерувати виключення
    }

    if (str[i] < '0') throw "Bad symbols";
    if (str[i] > '9') throw "Bad symbols";

    res = res + (str[i] - '0')*t;
    t *= 10;
    i--;
  }

  // якщо результат виходить за межі діапазону для 32-розрядних
  // цілочисельних значень, то згенерувати відповідне виключення
  if (res > INT32_MAX)
    throw "Out of range.";
  if (res < INT32_MIN)
    throw "Out of range.";
  return res;
}

void main()
{
  int d;

  // блок try...catch розміщується у функції main() вищого рівня,
  // а виключення генерується у функції StrToInt2() нижчого рівня
  try {
    d = StrToInt2("19125");
    cout << "d = " << d;
  }
  catch (const char* e)
  {
    cout << e << endl;
  }
}

Як видно з виденаведеного коду, генерувати виключення оператором throw можна в іншій функції, виклик якої включений у блок try. Отже, функція у своєму тілі може генерувати виключення.

Результат виконання програми

d = 19125

Якщо виклик функції StrToInt2() перенести за межі оператора try

void main()
{
  int d;

  try {
    //d = StrToInt2("19125");
    //cout << "d = " << d;
  }
  catch (const char* e)
  {
    cout << e << endl;
  }

  // виклик функції за межами оператора try
  d = StrToInt2("у19125");
}

то виключні ситуації у функції StrToInt2() оброблятись не будуть. При виникненні виключної ситуації у функції StrToInt2() компілятор згенерує власну помилку

Exception Unhandled

що означає, що виключення необроблене.

 

8. Приклад програми, яка генерує виключення в одній функції, а перехоплює в іншій функції

У прикладі, у функції нижнього рівня GenerateException() генерується виключення типу const char*. Функція перевіряє допустимі межі вхідного параметру index.

У функції верхнього рівня ProcessException() відбувається виклик функції GenerateException(). Цей виклик взятий в блок try.

Текст програми наступний:

// Приклад Програми, яка генерує виключення в одній функції, а перехоплює його в іншій
// Функція згенерує виключення "Out of index",
// якщо значення index знаходиться за межами діапазону 0..9
void GenerateException(int index)
{
  if ((index < 0) || (index > 9))
    throw "Out of index";
}

// Функція, яка перехоплює виключення "Out of index"
void ProcessException()
{
  int index;
  cout << "index = ";
  cin >> index;

  // 1. Викликати виключну ситуацію без обробки,
  // компілятор видасть повідомлення "Exception unhandled"
  // GenerateException(-3);

  // 2. Викликати виключну ситуацію з обробкою блоком try...catch
  try {
    GenerateException(index); // виклик функції, яка генерує виключення
    cout << "OK!" << endl; // якщо index в межах 0..9, то OK!
  }
  catch (const char* e)
  {
    cout << "Error: " << e << endl;
  }
}

void main()
{
  ProcessException();
}

Результат виконання програми

index = -5
Error: Out of index

 

9. Використання блоку catch(…). Перехоплення усіх можливих виключних ситуацій. Приклад

Бувають випадки, коли потрібно перехватити усі виключні ситуації підряд. Для цього, в C++ використовується блок catch(…), який має таку загальну форму

catch(...)
{
  // Обробка усіх виключних ситуацій
  // ...
}

Приклад. У прикладі демонструється використання блоку catch(…) для обробки ситуацій будь-якого типу.

У програмі реалізовано:

  • функція DivNumbers(), яка повертає результат ділення двох чисел, введених з клавіатури. У функції генерується виключення типу int, якщо значення дільника рівне 0;
  • функція SqRoot(), яка повертає корінь з від’ємного числа. У функції генерується виключення типу const char*, якщо значення параметру number від’ємне;
  • функція ProcessException(). Ця функція демонструє роботу функцій DivNumbers() та SqRoot(). У функції використовується інструкція try…catch().
// Приклад. Демонстрація використання блоку catch
// Функція, яка ділить 2 числа і повертає результат
double DivNumbers(double a, double b)
{
  if (b == 0) throw 1;
  return a / b;
}

// Функція, яка повертає корінь з від'ємного числа
double SqRoot(double number)
{
  if (number < 0) throw "Negative number";
    return sqrt(number);
}

// Демонстрація блоку catch(...)
void ProcessException()
{
  double v;

  // цикл відображення та виклику потрібної функції
  while (1)
  {
    cout << "Input a function to call (1-2, 3-exit): " << endl;
    cout << "1-DivNumbers. 2-SqRoot" << endl;
    cout << ">>";
    cin >> v;

    // Викликати різні варіанти функцій
    try {
      if (v == 1) // функція DivNumbers
      {
        double a, b;
        cout << "DivNumbers(double a, double b)" << endl;

        // ввести a, b
        cout << "a = "; cin >> a;
        cout << "b = "; cin >> b;

        // Викликати функцію DivNumbers()
        double c = DivNumbers(a, b);
        cout << "c = " << c << endl;
      }
      if (v == 2)
      {
        double x, num;
        cout << "SqRoot(double num)" << endl;
        cout << "num = "; cin >> num;

        // Викликати функцію SqRoot()
        x = SqRoot(num);
        cout << "x = " << x << endl;
      }
      if (v == 3) break;
    }
    catch (const char* e)
    {
      cout << "Error. Text = " << e << endl;
    }
    catch (...) // усі інші типи виключень
    {
      cout << "Error in block catch(...)." << endl;
    }
  }
}

void main()
{
  ProcessException();
}

Як видно з тексту функції ProcessException() виклик функцій DivNumbers() та SqRoot() взято в блок try…catch

// Викликати різні варіанти функцій
try {
  ...
}
catch (const char* e)
{
  cout << "Error. Text = " << e << endl;
}
catch (...) // усі інші типи виключень
{
  cout << "Error in block catch(...)." << endl;
}

У блоці try…catch обробляються

  • виключення типу const char*;
  • усі інші види виключень. У цьому випадку використовується інструкція catch(…).

Результат роботи програми

Input a function to call (1-2, 3-exit):
1-DivNumbers.   2-SqRoot
>>2
SqRoot(double num)
num = -4
Error. Text = Negative number
Input a function to call (1-2, 3-exit):
1-DivNumbers.   2-SqRoot
>>1
DivNumbers(double a, double b)
a = 3
b = 0
Error in block catch(...).
Input a function to call (1-2, 3-exit):
1-DivNumbers.   2-SqRoot
>>1
DivNumbers(double a, double b)
a = 2
b = 5
c = 0.4
Input a function to call (1-2, 3-exit):
1-DivNumbers.   2-SqRoot
>>3

 


Зв’язані теми