Студопедия

КАТЕГОРИИ:


Архитектура-(3434)Астрономия-(809)Биология-(7483)Биотехнологии-(1457)Военное дело-(14632)Высокие технологии-(1363)География-(913)Геология-(1438)Государство-(451)Демография-(1065)Дом-(47672)Журналистика и СМИ-(912)Изобретательство-(14524)Иностранные языки-(4268)Информатика-(17799)Искусство-(1338)История-(13644)Компьютеры-(11121)Косметика-(55)Кулинария-(373)Культура-(8427)Лингвистика-(374)Литература-(1642)Маркетинг-(23702)Математика-(16968)Машиностроение-(1700)Медицина-(12668)Менеджмент-(24684)Механика-(15423)Науковедение-(506)Образование-(11852)Охрана труда-(3308)Педагогика-(5571)Полиграфия-(1312)Политика-(7869)Право-(5454)Приборостроение-(1369)Программирование-(2801)Производство-(97182)Промышленность-(8706)Психология-(18388)Религия-(3217)Связь-(10668)Сельское хозяйство-(299)Социология-(6455)Спорт-(42831)Строительство-(4793)Торговля-(5050)Транспорт-(2929)Туризм-(1568)Физика-(3942)Философия-(17015)Финансы-(26596)Химия-(22929)Экология-(12095)Экономика-(9961)Электроника-(8441)Электротехника-(4623)Энергетика-(12629)Юриспруденция-(1492)Ядерная техника-(1748)

Namespace CounterNameSpace 8 страница




Щоб виняток було перехоплено, необхідно забезпечити його "викид" в try -блоці.

Загальний формат настанови throw має такий вигляд:

throw exception;

У цьому записі за допомогою елемента exception задається виняток, що згенерується настановою throw. Якщо цей виняток підлягає перехопленню, то настанова throw повинна бути виконана або в самому блоці try, або в будь-якій функції, які викликається з нього (тобто прямо або опосередковано).

Варто знати! Якщо у програмі забезпечується "викид" винятку, для якого не передбачена відповідна catch -настанова, то відбудеться аварійне завершення роботи програми, що викликається стандартною бібліотечною функцією terminate (). За замовчуванням функція terminate () викликає функцію abort () для зупинки програми, але при бажанні можна визначити власний обробник для її завершення. За подробицями щодо оброблення цієї ситуації необхідно звернутися до документації, що додається до Вашого компілятора.

Розглянемо простий приклад оброблення винятків засобами мови C++.

Код програми 18.1. Простий приклад оброблення винятків

#include <iostream> // Для потокового введення-виведення

using namespace std; // Використання стандартного простору імен

 

int main ()

{

cout << "Початок.\n";

 

try { // Початок try-блоку

cout << "B try-блоці\n";

throw 99; // Генерування помилки

cout << "Ця настанова не буде виконана.";

}

catch (int izm) { // Перехоплення помилки

cout << "Значення перехопленого винятку дорівнює: "

<< izm << "\n";

}

 

cout << "Кінець програми";

 

getch (); return 0;

}

У процесі виконання ця програма відображає на екрані такі результати:

Початок.

У try -блоці

Значення перехопленого винятку дорівнює: 99

Кінець програми

Розглянемо уважно код цієї програми. Як бачите, тут try -блок містить три настанови, а настанова catch (int izm) призначена для оброблення винятку цілочисельного типу. У цьому try -блоці виконуються тільки дві з трьох настанов: cout і throw. Після генерування винятку керування передається catch -виразу, при цьому виконання try -блоку припиняється. Необхідно розуміти, що catch -настанова невикликається, а просто з неї продовжується виконання програми після "викиду" винятку. Стек програми автоматично налаштовується відповідно до ситуації, що створилася. Тому cout -настанова, що є наступною після throw -настанови, ніколи не виконається.

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

Як ми вже зазначали вище, тип винятку повинен збігатися з типом, заданим у catch -настанові. Наприклад, якщо в попередній програмі тип int, який було вказано в catch -виразі, замінити типом double, то виняток перехоплено не буде і відбудеться аварійне завершення роботи програми. Ось як виглядають наслідки внесення такої зміни.

Код програми 18.2. Цей приклад працювати не буде

#include <iostream> // Для потокового введення-виведення

using namespace std; // Використання стандартного простору імен

 

int main ()

{

cout << "Початок.\n";

 

try { // Початок try-блоку

cout << "У try -блоці\n";

throw 99; // генерування помилки

cout << "Ця настанова не буде виконана.";

}

catch (double izm) { // Перехоплення винятку типу int не відбудеться.

cout << "Значення перехопленого винятку дорівнює: ";

cout << izm << "\n";

}

 

cout << "Кінець програми";

 

getch (); return 0;

}

Такі результати виконання цієї програми пояснюються тим, що винятки цілочисельного типу не перехоплюється настановою catch (double izm).

Початок.

У try -блоці

Ця настанова не буде виконана.

18.1.2. Використання функцій exit() і abort() для завершення роботи програми

Функції exit () і abort () входять до складу стандартної бібліотеки C++ і часто використовують під час програмування мовою C++. Обидві вони забезпечують завершення роботи програми, але відбувається це по-різному.

Виклик функції exit () негайно приводить до "правильного" припинення роботи програми[69]. Зазвичай цей спосіб завершення роботи використовують для зупинки програми під час виникнення непоправної помилки, яка робить подальше її виконання безглуздим або небезпечним. Для використання функції exit () потрібно залучити до програми заголовок < cstdlib >. Її прототип має такий вигляд:

void exit (int status);

Оскільки функція exit () викликає негайне завершення роботи програми, то вона не передає керування процесу, що її викликає, і не повертає ніякого значення. Проте процесу, що її викликає, як код завершення передається значення параметра status. За домовленістю нульове значення параметра status вказує на успішне завершення роботи програми. Будь-яке інше його значення свідчить про завершення роботи програми помилковим. Для індикації успішного завершення роботи можна також використовувати константу EXIT_SUCCESS, а для індикації помилки – константу EXIT_FAILURE. Ці константи визначаються у заголовку < cstdlib >.

Прототип функції abort () має такий вигляд:

void abort ();

Аналогічно функції exit () функція abort () викликає негайне завершення роботи програми. Але, на відміну від функції exit (), вона не повертає операційній системі ніякої інформації про статус завершення роботи програми і не виконує стандартної ("правильної") послідовності дій під час зупинки програми. Для використання функції abort () потрібно залучити до програми заголовок < cstdlib >. Функцію abort () можна назвати аварійним "стоп-краном" для С++-програми. Її необхідно використовувати тільки після виникнення непоправної помилки.

Останнє повідомлення про аварійне завершення роботи програми (Abnormal program termination) може відрізнятися від наведеного в результатах виконання попереднього прикладу. Це залежить від використовуваного Вами компілятора.

Виняток, що генерує функція, яку було викликано з try -блоку, можна перехопити цим самим try -блоком. Розглянемо, наприклад, таку цілком коректну програму.

Код програми 18.3. Демонстрація механізму генерування винятку функцією, що викликається з try-блоку

#include <iostream> // Для потокового введення-виведення

using namespace std; // Використання стандартного простору імен

 

void Xtest(int test)

{

cout << "У функції Xtest() значення test дорівнює: "

<< test << "\n";

if (test) throw test;

}

 

int main ()

{

cout << "Початок.\n";

 

try { // Початок try-блоку

cout << "B try-блоці\n";

Xtest(0);

Xtest(1);

Xtest(2);

}

 

catch (int izm) { // Перехоплення помилки

cout << "Значення перехопленого винятку дорівнює: ";

cout << izm << "\n";

}

 

cout << "Кінець програми";

 

getch (); return 0;

}

Внаслідок виконання цієї програми на моніторі буде відображено такі результати:

Початок.

У try -блоці

У функції Xtest() значення test дорівнює: 0

У функції Xtest() значення test дорівнює: 1

Значення перехопленого винятку дорівнює: 1

Кінець програми

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

Код програми 18.4. Демонстрація локалізації блоку try у рамках роботи самої функції

#include <iostream> // Для потокового введення-виведення

using namespace std; // Використання стандартного простору імен

 

// Функціонування блоків try / catch поновлюється

// під час кожного входження у функцію.

void Xhandler(int test)

{

try {

if (test) throw test;

}

catch (int izm) {

cout << "Перехоплення! Виняток №: " << izm << "\n";

}

}

 

int main ()

{

cout << "Початок.\n";

 

Xhandler(1);

Xhandler(2);

Xhandler(0);

Xhandler(3);

 

cout << "Кінець програми";

 

getch (); return 0;

}

У процесі виконання цієї програми на моніторі будуть відображені такі результати:

Початок.

Перехоплення! Виняток №: 1

Перехоплення! Виняток №: 2

Перехоплення! Виняток №: 3

Кінець програми

Як бачите, програма згенерувала три різних винятки. Після кожного винятку функція Xhandler() передавала керування у функцію main (). Коли вона знову викликалася, поновлювалося і оброблення винятків.

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

18.1.3. Перехоплення винятків класового типу

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

Код програми 18.5. Демонстрація перехоплення винятків класового типу

#include <iostream> // Для потокового введення-виведення

#include <cstring> // Для роботи з рядковими типами даних

using namespace std; // Використання стандартного простору імен

 

class myException {

public:

char strMas[80];

myException() { *strMas = 0;}

myException(char *s) { strcpy (strMas, s);}

};

 

int main ()

{

int a, b;

try {

cout << "Введіть чисельник і знаменник: ";

cin >> а >> b;

if (!b) throw myException("Ділити на нуль не можна!");

else cout << "Частка дорівнює " << a/b << "\n";

}

catch (myException e) { // Перехоплення помилки

cout << e.strMas << "\n";

}

 

getch (); return 0;

}

Один з можливих результатів виконання цієї програми.

Введіть чисельник і знаменник: 10 0

Ділити на нуль не можна!

Після запуску програми користувачу пропонується ввести чисельник і знаменник. Якщо знаменник дорівнює нулю, то створюється об'єкт класу myException, який містить інформацію про спробу ділення на нуль. Таким чином, клас myException інкапсулює інформацію про помилку, яка потім використовується обробником винятків для повідомлення користувача про те, що трапилося.

Безумовно, реальні винятки класового типу набагато складніші за клас myException. Як правило, створення винятків класового типу має сенс у тому випадку, якщо вони інкапсулюють інформацію, яка б дала змогу обробнику винятків ефективно справитися з помилкою і за змогою відновити роботоздатність програми.

18.1.4. Використання декількох catch-настанов

Як ми вже зазначали вище, з try -блоком можна пов'язувати не одну, а декілька catch -настанов. Насправді саме така практика і застосовується найчастіше. Але при цьому всі catch -настанови повинні перехоплювати винятки різних типів. Наприклад, у наведеному нижче коді програми забезпечується перехоплення як цілих чисел, так і покажчиків на символи.

Код програми 18.6. Демонстрація механізму використання декількох catch-настанов

#include <iostream> // Для потокового введення-виведення

using namespace std; // Використання стандартного простору імен

 

// Тут можливе перехоплення винятків різних типів.

void Xhandler(int test)

{

try {

if (test) throw test;

else throw "Значення дорівнює нулю.";

}

catch (int izm) {

cout << "Перехоплення! Виняток №: " << izm << "\n";

}

 

catch (char *str) {

cout << "Перехоплення рядка: " << str << "\n";

}

}

 

int main ()

{

cout << "Початок.\n";

 

Xhandler(1);

Xhandler(2);

Xhandler(0);

Xhandler(3);

 

cout << "Кінець програми";

 

getch (); return 0;

}

Внаслідок виконання цієї програми на моніторі буде відображено такі результати:

Початок.

Перехоплення! Виняток №: 1

Перехоплення! Виняток №: 2

Перехоплення рядка: Значення дорівнює нулю.

Перехоплення! Виняток №: 3

Кінець програми

Як бачите, кожна catch -настанова відповідає тільки за винятки "свого" типу.

У загальному випадку catch -вирази перевіряються у порядку їх проходження, тобто виконується тільки той catch -блок, у якому тип заданого винятку збігається з типом винятку, що згенерувався. Вся решта catch -блоків ігнорується.

Перехоплення винятків базового класу

Важливо розуміти, як виконуються catch -настанови, пов'язані з похідними класами. Йдеться про те, що catch -вираз для базового класу "відреагує збігом" на винятки будь-якого похідного типу (тобто типу, виведеного з цього базового класу). Отже, якщо потрібно перехоплювати винятки як базового, так і похідного типів, то у catch -послідовності catch -настанову для похідного типу необхідно помістити перед catch -настановою для базового типу. Інакше catch -вираз для базового класу перехоплюватиме (крім "своїх") і винятки всіх похідних класів. Розглянемо, наприклад, наведену нижче програму.

Код програми 18.7. Демонстрація перехоплення винятків базових і похідних типів

#include <iostream> // Для потокового введення-виведення

using namespace std; // Використання стандартного простору імен

 

class bClass{ // Оголошення класового типу

};

class dClass: public bClass{ // Оголошення класового типу

};

 

int main ()

{

dClass derived;

 

try { throw derived; }

catch (bClass b_ob)

{ cout << "Перехоплення винятку базового класу.\n";}

catch (dClass d_ob)

{ cout << "Це перехоплення не відбудеться.\n";}

 

getch (); return 0;

}

Оскільки тут об'єкт derived – це об'єкт класу dClass, який виведений з базового класу bClass, то виняток типу derived завжди перехоплюватиметься першим catch -виразом; друга ж catch -настанова при цьому ніколи не виконається. Одні компілятори відреагують на такий стан речей застережливим повідомленням, інші можуть видати повідомлення про помилку. У будь-якому випадку, щоб виправити ситуацію, достатньо поміняти порядок слідування цих catch -настанов на протилежний.

18.2. Варіанти оброблення винятків

Крім розглянутих вище, існують і інші С++-засоби оброблення винятків, які створюють певні зручності для програмістів. Про них і піде мова у цьому розділі.

18.2.1. Перехоплення всіх винятків

Іноді варто створити спеціальний обробник для перехоплення всіх винятків, а не винятків тільки певного типу. Для цього достатньо використовувати такий формат catch -блоку:

catch (...) {

// Оброблення всіх винятків

}

У цьому записі занесена в круглі дужки багатокрапка забезпечує збіг з будь-яким типом даних.

Використання формату catch (...) продемонстровано в такій програмі.

Код програми 18.8. Демонстрація перехоплення винятків усіх типів

#include <iostream> // Для потокового введення-виведення

using namespace std; // Використання стандартного простору імен

 

void Xhandler(int test)

{

try {

if (test == 0) throw test; // Генерує int -винятки

if (test == l) throw 'a'; // Генерує char -винятки

if (test == 2) throw 123.23; // Генерує double -винятки

}

catch (...) { // Перехоплення всіх винятків

cout << "Перехоплення!\n";

}

}

 

int main ()

{

cout << "Початок.\n";

 

Xhandler(0);

Xhandler(1);

Xhandler(2);

cout << "Кінець програми";

 

getch (); return 0;

}

Внаслідок виконання цієї програми на моніторі буде відображено такі результати:

Початок.

Перехоплення!

Перехоплення!

Перехоплення!

Кінець програми

Як бачите, всі три throw -винятки перехоплені за допомогою однієї catch -настанови.

Часто доцільно використовувати настанову catch (...) як останній "рубіж" catch -послідовності. У цьому випадку вона забезпечує перехоплення винятків "всіх решти" типів (тобто не передбачених попередніми catch -виразами). Наприклад, розглянемо ще одну версію попередньої програми, у якій безпосередньо забезпечується перехоплення винятків цілочисельного типу, а перехоплення всіх решти можливих винятків "звалюється на плечі" настанови catch (...).

Код програми 18.9. Демонстрація механізму використання настанови catch(...) для перехоплення винятків "усіх решти" типів

#include <iostream> // Для потокового введення-виведення

using namespace std; // Використання стандартного простору імен

 

void Xhandler(int test)

{

try {

if (test == 0) throw test; // Генерує int -винятки

if (test == l) throw 'a'; // Генерує char -винятки

if (test == 2) throw 123.23; // Генерує double -винятки

}

catch (int izm) { // Перехоплює int -винятки

cout << "Перехоплення " << izm << "\n";

}

catch (...) { // Перехоплює всю решту винятків

cout << "Перехоплення-перехоплення!\n";

}

}

 

int main ()

{

cout << "Початок.\n";

 

Xhandler(0);

Xhandler(1);

Xhandler(2);

 

cout << "Кінець програми";

 

getch (); return 0;

}

Результати, що було згенеровано у процесі виконання цієї програми, є такими:

Початок.

Перехоплення 0

Перехоплення-перехоплення!

Перехоплення-перехоплення!

Кінець програми

Як підтверджує цей приклад, використання формату catch (...) як "останнього рубежу" catch -послідовності – це зручний спосіб перехопити всі винятки, які Вам не хочеться обробляти в безпосередньому вигляді. Окрім того, перехоплюючи абсолютно всі винятки, Ви запобігаєте можливості аварійного завершення роботи програми, яке може бути викликане якимсь непередбаченим (а значить, необробленим) винятком.

18.2.2. Обмеження, що накладаються на тип винятків, які генеруються функціями

Існують засоби, які дають змогу обмежити тип винятків, які може генерувати функція за межами свого тіла. Можна також захистити функцію від генерування будь-яких винятків взагалі. Для формування цих обмежень необхідно внести у визначення функції throw -вираз. Загальний формат визначення функції з використанням throw -виразу має такий вигляд:

тип ім'я_функції (список_аргументів) throw (список_імен_типів)

{

//...

}

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

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

На прикладі наведеної нижче програми показано, як можна обмежити типи винятків, які здатна генерувати функція.

Код програми 18.10. Обмеження типів винятків, що генеруються функцією

#include <iostream> // Для потокового введення-виведення

using namespace std; // Використання стандартного простору імен

 

// Ця функція може генерувати винятки тільки типу int, char і double.

void Xhandler(int test) throw (int, char, double)

{

if (test == 0) throw test; // Генерує int -винятки

if (test == l) throw 'a'; // Генерує char -винятки

if (test == 2) throw 123.23; // Генерує double -винятки

}

 

int main ()

{

cout << "Початок.\n";

try {

Xhandler(0); // Спробуйте також передати

// функції Xhandler() аргументи 1 і 2.

}

 

catch (int izm) { cout << "Перехоплення int -винятку.\n"; }

 

catch (char c) { cout << "Перехоплення char -винятку.\n"; }

 

catch (double d) { cout << "Перехоплення double -винятку.\n"; }

 

cout << "Кінець програми";

 

getch (); return 0;

}

У цій програмі функція Xhandler() може генерувати винятки тільки типу int, char і double. Під час спроби згенерувати винятки будь-якого іншого типу відбудеться аварійне завершення роботи програми (дякуючи виклику функціям unexpected()). Щоб переконатися у цьому, видаліть з throw -списку, наприклад, тип int і перезапустіть заново програму.

Важливо розуміти, що діапазон винятків, дозволених для генерування функції, можна обмежувати тільки типами, що генеруються нею в try -блоці, з якого була викликана. Іншими словами, будь-який try -блок, розташований в тілі самої функції, може генерувати винятки будь-якого типу, якщо вони перехоплюються в тілі тієї ж самої функції. Обмеження застосовується тільки для ситуацій, коли "викид" винятків відбувається за межі функції.

Наступна зміна помішає функції Xhandler() генерувати будь-які зміни.

// Ця функція взагалі не може генерувати винятки!

void Xhandler(int test) throw ()

{

/* Наведені нижче настанови більше не працюють.

Тепер вони можуть викликати тільки аварійне

завершення роботи програми. */

if (test == 0) throw test;

if (test == l) throw 'a';

if (test == 2) throw 123.23;

}

Варто знати! На момент написання цього навчального посібника середовище Visual C++ не забезпечувало для функції заборону генерувати винятки, тип яких не задано в throw -виразі. Це свідчить про нестандартну поведінку даного середовища. Проте Ви все одно можете задавати те, що "обмежує" throw -вираз, але воно у цьому випадку відіграватиме тільки повідомне значення.

18.2.3. Повторне генерування винятку




Поделиться с друзьями:


Дата добавления: 2014-11-29; Просмотров: 577; Нарушение авторских прав?; Мы поможем в написании вашей работы!


Нам важно ваше мнение! Был ли полезен опубликованный материал? Да | Нет



studopedia.su - Студопедия (2013 - 2024) год. Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав! Последнее добавление




Генерация страницы за: 0.209 сек.