Obsługa wyjątków zapewnia reakcję na nietypowe zdarzenia w programie, a w szczególności sytuacje błędne. Przekazuje ona sterowanie do specjalnych procedur obsługi sytuacji wyjątkowych zwanych handlerami. Podstawowymi słowami kluczowymi dotyczącymi zajmowania się wyjątkami są:
try, throw, catch
W bloku try
znajdują się instrukcje, które mogą rzucać wyjątki. Kiedy nastąpi sytuacja wyjątkowa w jednej z instrukcji znajdujących się w tym bloku, sterowanie zostaje przekazane do odpowiedniego bloku catch
. Za blokiem try
musi pojawić się co najmniej jeden blok catch
, w przeciwnym wypadku następuje błąd kompilacji.
Służy do rzucania obiektów w sytuacji wyjątkowej. Działanie funkcji, w której nastąpi instrukcja throw
, zostaje natychmiast przerwane, a rzucony obiekt zostaje natychmiast przekazany do bloku catch
jako argument, gdzie zostaje obsłużona sytuacja wyjątkowa.
Blok ten łapie obiekty konkretnego typu rzucone w bloku try
. Jeśli typ rzuconego obiektu zgadza się z typem argumentu catch
następuje obsługa sytuacji wyjątkowej przez ten jeden blok catch
Kolejność bloków catch
ma znaczenie, ponieważ kompilator wybiera PIERWSZY blok, który może złapać rzucony wyjątek, nawet jeśli kolejne bloki catch
pasują lepiej. Każdy z bloków catch
znajdujący się pod jednym blokiem try
musi przyjmować inny argument.
catch(…)
- może łapać obiekty wszystkich typów.
try { throw 1; } catch(int i) { cout << "Wyjątek! Nr " << i << endl; }
Po rzuceniu wyjątku i obsłużeniu go w bloku catch
nie następuje powrót do uprzednio wykonywanej funkcji, gdzie rzucono wyjątek za pomocą throw
. Wykonywane są instrukcje znajdujące się po sekwencji bloków try
i catch
.
W bloku catch
możliwe jest rzucenie obiektu ‘dalej’, do dalszej obsługi za pomocą throw;
bez argumentów.
Podczas deklaracji funkcji można ograniczyć typy wyjątków, które rzuca dana funkcja poprzez dodanie throw (typ)
:
int funkcja(int i) throw (int); //funkcja rzuca wyjątki typu int int funkcja(int i) throw' (); //funkcja nie może rzucać żadnych wyjątków int funkcja(int i); //funkcja rzuca wyjątki dowolnego typu
Jeśli w bloku try
powstały obiekty automatyczne, to w momencie, gdy wyjątek jest rzucany i sterowanie przechodzi do bloku catch
, następuje odwikłanie stosu. Jest to usunięcie wszystkich obiektów automatycznych, które zdarzyły powstać w bloku try
w kolejności odwrotnej do tworzenia.
try { Object a; Object b; Object c; throw 1; Object d; } // odwikłanie stosu: uruchomienie destruktora klasy Object, najpierw dla obiektu c, potem b, na końcu a, // nie zostanie on uruchomiony dla obiektu d, bo nie został on jeszcze utworzony. catch (int i) { cout << "Wyjątek! Nr " << i << endl; }
Funkcja ta kończy program zamykając wszystkie pliki i opróżniając wszystkie bufory.
Funkcja brutalnie kończy program, ‘nie sprzątając’ po sobie.
Gdy żaden z bloków catch
nie potrafi obsłużyć wyjątku, wywoływana jest funkcja terminate()
. Funkcja ta zostanie również wywołana przy odwikłaniu stosu, gdy destruktor obiektu rzuca nowy wyjątek lub gdy pomiędzy rzuceniem obiektu, a złapaniem go w bloku catch
nastąpi wywołanie konstruktora kopiującego, który próbuje rzucić nowym wyjątkiem. terminate()
wywołuje funkcję abort()
, aby zmienić to wywołanie na funkcję wybraną przez programistę można to zrobić za pomocą funkcji (void(*wsk)()) set_terminate(void (*wsk)())
.
Gdy funkcja deklaruje konkretne typy rzucanych wyjątków, a rzuca wyjątek innego typu, wyjątek ten nie może zostać obsłużony. Wywoływana jest wtedy funkcja unexpected()
, która domyślnie na koniec wywołuje funkcję terminate()
. Funkcją (void (*wsk)()) set_unexpected(void (*wsk)())
można zmienić wykonywanie terminate()
, na rzecz wykonania funkcji wybranej przez programistę.
Biblioteka standardowa dostarcza klasę bazową dla obiektów, które są rzucane jako wyjątki. Zdefiniowana jest ona w <exception>
(przestrzeń nazw - std
).
wyjątek | opis |
---|---|
bad_alloc | rzucany przez new przy nieudanej alokacji pamięci |
bad_cast | rzucany przez dynamic_cast |
bad_exception | rzucany gdy wyjątek nie pasuje do żadnego bloku catch |
bad_typeid | rzucany przez typeid |
ios_base::failure | rzucany przez funkcje z biblioteki <iostream> |
/* * Weronika Trybek I1ISI * Zad. 5 Opis wyjątków w bibliotece standardowej. */ #include <iostream> using namespace std; /* * Klasa Sensor oraz klasa SmokeDetector po niej dziedzicząca, * to proste klasy, posiadające konstruktory i destruktory wyświetlające tekst * w celu łatwiejszego zobrazowania mechanizmu obsługi wyjątków. */ class Sensor { protected: int number_; char* text_; bool alarm_; public: Sensor() : number_(0), text_("Zwykly Czujnik"), alarm_(false) { cout << " Konstruktor domyslny Czujnika " << number_ << endl; } Sensor(int n, char* t) : number_(n), text_(t), alarm_(false) { cout << " Konstruktor Czujnika, "; } Sensor(const Sensor& s) { number_ = s.number_ + 1; text_ = s.text_; alarm_ = s.alarm_; cout << " Konstruktor kopiujacy Czujnika " << s.number_; if( text_ == "Zwykly Czujnik" ) cout << ", powstaje " << number_ << endl; else cout << ", "; } virtual ~Sensor() { cout << " Destruktor Czujnika " << number_ << endl; } virtual void alarmOn() { alarm_ = true; throw *this; } void alarmOff() { alarm_ = false; } }; class SmokeDetector : public Sensor { static int counter_; public: SmokeDetector() : Sensor(++counter_, "Czujnik Dymu") { cout << "Czujnika Dymu " << number_ << endl; } SmokeDetector(const SmokeDetector& m) : Sensor(m) { ++counter_; number_ = counter_; cout << "Czujnika Dymu, powstaje " << number_ << endl; } ~SmokeDetector() { --counter_; cout << " Destruktor Czujnika Dymu " << number_; } void alarmOn() { alarm_ = true; throw *this; } }; int SmokeDetector::counter_ = 0; int main() { cout << "\n\n***Obsluga wyjatku, bez referencji do obiektu w argumencie catch***\n\n"; try { Sensor sensor; // wywołanie konstruktora domyślnego Sensor (0) sensor.alarmOn(); // throw sensor; -> wywołanie konstruktora kopiującego Sensor (1) cout << "Jestem tutaj!\n"; // tekst ten nigdy nie będzie wyświetlony, bo sterowanie przechodzi do bloku catch } // sporządzenie kopii statycznej dla catch -> wywołanie konstruktora kopiującego Sensor (2) // odwikłanie stosu -> wywołanie destruktora Sensor (0) catch(Sensor s) { cout << "catch(Sensor s)\n"; } // koniec bloku catch -> wywołanie destruktora Sensor (2) i (1) cout << "\n\n***Obsluga wyjatku, z referencja do obiektu w argumencie catch***\n\n"; try { Sensor sensor; // wywołanie konstruktora domyślnego Sensor (0) sensor.alarmOn(); // throw sensor; -> wywołanie konstruktora kopiującego Sensor (1) } // odwikłanie stosu -> wywołanie destruktora Sensor (0) catch(Sensor& s) // wybrany catch { cout << "catch(Sensor& s)\n"; } // koniec bloku catch -> wywołanie destruktora Sensor (1) catch(...) // nie wchodzi tu juz, bo wyjątek został obsłuzony przez wczesniejszy catch { cout << "catch(...)\n"; } cout << "\n\n***Obsluga wyjatku, dziedziczenie oraz zagniezdzone bloki try i catch***\n\n"; try { // zewnetrzny try try // wewnetrzny try { SmokeDetector smokeDet; // wywołanie konstruktora Sensor (1) oraz SmokeDetector (1) SmokeDetector smokeDet2; // wywołanie konstruktora Sensor (2) oraz SmokeDetector (2) smokeDet.alarmOn(); // throw SmokeDetector;-> wywołanie konstruktora kopiującego Sensor (3) i SmokeDetector (3) } // odwikłanie stosu -> wywołanie destruktorów SmokeDetector (2), Sensor (2) oraz SmokeDetector (1), Sensor (1) catch(Sensor& s) // wybrany catch { cout << "catch(Sensor& s)\n"; throw; // dalsze rzucenie tego samego obiektu } // koniec bloku catch -> nie wywołany jest destruktor SmokeDetector, bo obiekt rzucony dalej catch(SmokeDetector& s) // nie wchodzi tu juz, mimo iż dopasowanie byłoby lepsze, { // bo wyjatek zostal obsluzony przez wczesniejszy blok catch cout << "catch(MotionSensor& s)\n"; } } catch(SmokeDetector& s) // wybrany catch, bo wyjątek został rzucony ponownie { cout << "zewnetrzny catch(MotionSensor& s)\n"; } // koniec bloku catch -> wywołanie destruktora SmokeDetector, Sensor (2) cout << "\n\n***Obsluga wyjatku, odwiklanie stosu sprzata tylko to, co zostalo \n utworzone na stosie," << " nie usuwa obiektow utworzonych za pomoca new***\n\n"; try { Sensor* sensor = new Sensor; // wywołanie konstruktora Sensor (0) sensor->alarmOn(); // throw sensor; -> wywołanie konstruktora kopiującego Sensor nr 1 SmokeDetector smokeDet; // ten obiekt nigdy nie bedzie utworzony } // odwikłanie stosu -> wywołanie destruktora wskaznika na Sensor (0), ale sam Sensor (0) nie zostaje usuniety catch(Sensor& s) { cout << "catch(Sensor& s)\n"; } // koniec bloku catch -> wywołanie destruktora Sensor (1) catch(...) { cout << "catch(...)\n"; } return 0; }
***Obsluga wyjatku, bez referencji do obiektu w argumencie catch*** Konstruktor domyslny Czujnika 0 Konstruktor kopiujacy Czujnika 0, powstaje 1 Konstruktor kopiujacy Czujnika 1, powstaje 2 Destruktor Czujnika 0 catch(Sensor s) Destruktor Czujnika 2 Destruktor Czujnika 1 ***Obsluga wyjatku, z referencja do obiektu w argumencie catch*** Konstruktor domyslny Czujnika 0 Konstruktor kopiujacy Czujnika 0, powstaje 1 Destruktor Czujnika 0 catch(Sensor& s) Destruktor Czujnika 1 ***Obsluga wyjatku, dziedziczenie oraz zagniezdzone bloki try i catch*** Konstruktor Czujnika, Czujnika Dymu 1 Konstruktor Czujnika, Czujnika Dymu 2 Konstruktor kopiujacy Czujnika 1, Czujnika Dymu, powstaje 3 Destruktor Czujnika Dymu 2 Destruktor Czujnika 2 Destruktor Czujnika Dymu 1 Destruktor Czujnika 1 catch(Sensor& s) zewnetrzny catch(MotionSensor& s) Destruktor Czujnika Dymu 3 Destruktor Czujnika 3 ***Obsluga wyjatku, odwiklanie stosu sprzata tylko to, co zostalo utworzone na stosie, nie usuwa obiektow utworzonych za pomoca new*** Konstruktor domyslny Czujnika 0 Konstruktor kopiujacy Czujnika 0, powstaje 1 catch(Sensor& s) Destruktor Czujnika 1
— Weronika Trybek 2008/12/11 22:50