Wstęp do programowania obiektowego
Wyjątki
WYJĄTKI
Jeżeli w jakimś miejscu programu zajdzie nieoczekiwana sytuacja,
programista piszący ten kod powinien zasygnalizować o tym.
Dawniej polegało to na zwróceniu
specyficznej wartości, co nie było zbyt szczęśliwym rozwiązaniem, bo sygnał musiał być takiego samego typu jak wartość zwracana przez funkcję.
Obecnie w wielu językach mamy
specjalne mechanizmy do obsługi takich sytuacji.
W przypadku obsługi sytuacji wyjątkowej mówi się o obiekcie sytuacji wyjątkowej, co często
zastępowane jest słowem "wyjątek".
Wyjątki się zgłasza lub „rzuca”, w C++
służy do tego instrukcja throw.
Wyjątek zgłasza się po stwierdzeniu błędu, którego rozwiązanie nie jest możliwe na obecnym poziomie (w definicji obecnej funkcji/metody), ale może być możliwe gdzieś „wyżej”.
4
Tam gdzie spodziewamy się wyjątku umieszczamy blok try, w którym
umieszczamy "podejrzane" instrukcje.
Za tym blokiem muszą (tzn. musi być przynajmniej jedna) pojawić się bloki catch.
Przykład
try // w instrukcjach poniżej może coś się nie udać {
fun();
fun2(); //podejrzane funkcje, operacje, itp.
}
catch(char obj) {
//tu coś robimy, staramy się kryzys rozwiązać innym sposobem, np. anulujemy operację, żądamy dodatkowych danych od użytkownika, wypisujemy błąd itp.
}
Co dzieje się po zgłoszeniu wyjątku?
Przeszukiwany jest stos wywołań funkcji w poszukiwaniu takiej, która zawiera obsługę wyjątku danego typu (czyli odpowiednią instrukcję catch).
◦ Jeżeli odpowiednia obsługa wyjątku zostanie znaleziona, wyjątek jest obsługiwany.
◦ Jeżeli w trakcie poszukiwań nie został znaleziony pasujący blok obsługi wyjątku, wracamy do punktu wejściowego programu, czyli funkcji main.
Jeżeli i w main nie ma obsługi zgłoszonego wyjątku, program jest przerywany w trybie awaryjnym.
Sterowanie nigdy nie wróci do opuszczonych wcześniej funkcji!
Po obsłudze wyjątku, program może się znajdować w znacząco innym
stanie niż przed, co może utrudniać debugowanie.
Niektóre sytuacje są na tyle krytyczne dla wykonania programu, że nie ma sensu ich obsługiwać wyjątkami.
W instrukcji catch umieszczamy typ jakim będzie wyjątek. Rzucić możemy wyjątek typu int, char, ale również klasy definiowane przez użytkownika
Nazwa tego obiektu nie jest konieczna, ale jeżeli
chcemy mieć dostęp do obiektu to musimy ten obiekt jakoś nazwać (funkcjonuje to jako zmienna lokalna).
Bloków catch może być więcej, najczęściej tyle ile możliwych typów do złapania.
Co ważne jeżeli rzucimy wyjątek konkretnego typu to
"wpadnie" on do pierwszego odpowiadającego bloku catch nawet jeżeli inne nadają się lepiej. Dotyczy to zwłaszcza klas dziedziczonych.
catch(…) wychwytuje wszystkie typy wyjątków
#include<iostream>
using namespace std;
double Dziel(double a, double b) //funkcja zwraca iloraz a / b {
if (b == 0) //przez zero się nie dzieli
throw "dzielenie przez zero!"; //rzucamy wyjątek return a / b;
}
int main() {
try {
Dziel(10, 0);
}
catch(const char* w) {
cout<<"Wyjatek: "<<w;
}
cin.get();
return 0;
}
Dobrym zwyczajem (obowiązkowym w wielu współczesnych językach obiektowych, np.
Java) jest pisanie obok deklaracji funkcji jakie wyjątki może ona rzucać:
void fun(int aa) throw(char)
zapis ten w C++ oznacza że funkcja fun może wyrzucić tylko wyjątek typu char.
Zapis throw() oznacza, że funkcja nie może w ogóle wyrzucać wyjątków, a brak zapisu
oznacza, że może wyrzucić dowolny wyjątek.
Wiele języków posiada rozbudowaną
obsługę wyjątków, włącznie z utworzoną hierarchią klas wyjątkowych.
Standardowa biblioteka C++ oferuje klasę exception, zdefiniowaną w pliku
nagłówkowym <exception>, która
udostępnia dodatkową metodę wirtualną o nazwie what(char *), którą
nadpisujemy we własnej klasie wyjątku, dziedziczącego z exception, by
zwracała jego opis.
Blok catch przyjmuje obiekt typu exception przez referencję.
Wychwytuje więc też obiekty z klasy dziedziczącej (myexception).
Klasy wyjątków zdefiniowane w
bibliotece standardowej C++
Niektóre wyjątki z biblioteki standardowej C++
bad_alloc - niemożliwa alokacja pamięci
bad_cast - niemożliwa konwersja dynamiczna
bad_exception - nieobsłużony wyjątek (niepasujący do żadnego catch)
bad_typeid - wyrzucane przez typeid
ios_base::failure - wyrzucane przez funkcje z bilbioteki iostream
runtime_error – błędy wykrywane tylko podczas wykonania programu
Przechwytywanie wyjątku klasy bad_alloc
try {
int * myarray= new int[1000000000];
}
catch (bad_alloc&) {
cout << "Error allocating memory." << endl;
}
Przechwytywanie bad_alloc jako exception (klasy nadrzędnej)
#include <iostream>
#include <exception>
using namespace std;
int main () { try
{
int* myarray= new int[1000000000];
}
catch (exception& e) {
cout << "Standard exception: " << e.what() << endl;
}
return 0;
}
#include <iostream>
#include <exception>
using namespace std;
class myexception: public exception
{ virtual const char* what() const throw() { return "My exception happened";
} };
int main () { try
{ myexception myex; // tworzenie obiektu wyjątku throw myex; // zgłoszenie wyjątku
} catch (exception& e)
{ cout << e.what() << endl;
} return 0;
} 18
Tworzenie własnej klasy wyjątku
#include<iostream>
#include<string>
#include<cmath>
using namespace std;
double dziel(double, double) throw(string);
int main() {
try {
cout<<dziel(10, 2) << endl; // wykona się cout<<dziel(10, 0) << endl; // zgłosi wyjątek cout<<dziel(10, 5) << endl; // nie wykona się }
catch(string w) {
cout<<"Wyjatek: "<<w;
}
cin.get();
return 0;
}
double dziel(double a, double b) throw(string) if (b == 0) {
{string wyjatek = "dzielenie przez zero!";