[ Pobierz całość w formacie PDF ]
powinniśmy stosować. W zasadzie można je zamknąć w jedno stwierdzenie:
Nie powinno się wykorzystywać wyjątków tam, gdzie z powodzeniem wystarczają inne
techniki sygnalizowania i obsługi błędów.
Oznacza to, że:
nie powinniśmy na siłę dodawać wyjątków do istniejącego programu. Jeżeli po
przetestowaniu działa on dobrze i efektywnie bez wyjątków, nie ma żadnego
powodu, aby wprowadzać do kodu ten mechanizm
dla tworzonych od nowa, lecz krótkich programów wyjątki mogą być zbyt
potężnym narzędziem. Wysiłek włożony w jego zaprogramowanie (jak się zaraz
przekonamy - wcale niemały) nie musi się opłacać. Co oznacza pojęcie krótki
program , to już każdy musi sobie odpowiedzieć sam; zwykle uważa się, że
krótkie są te aplikacje, które nie przekraczają rozmiarami 1000-2000 linijek kodu
Widać więc, że nie każdy program musi koniecznie stosować ten mechanizm. Są
oczywiście sytuacje, gdy obyć się bez niego jest bardzo trudno, jednak nadużywanie
wyjątków jest zazwyczaj gorsze niż ich niedostatek. O obu sprawach (korzyściach
płynących z wyjątków i ich przesadnemu stosowaniu) powiemy sobie jeszcze pózniej.
Załóżmy jednak, że zdecydowaliśmy się wykorzystywać wyjątki. Jak poprawnie
zrealizować te intencje? Jak większość rzeczy w programowaniu, nie jest to trudne :)
Musimy mianowicie:
pomyśleć, jakie sytuacje wyjątkowe mogą wystąpić w naszej aplikacji i wyróżnić
wśród nich poszczególne rodzaje, a nawet pewną hierarchię. To pozwoli na
stworzenie odpowiednich klas dla obiektów wyjątków, czym zajmiemy się w
pierwszym paragrafie
Zaawansowane C++
494
we właściwy sposób zorganizować obsługę wyjątków - chodzi głównie o
rozmieszczenie bloków try i catch. Ta kwestia będzie przedmiotem drugiego
paragrafu
Potem możemy już tylko mieć nadzieję, że nasza ciężko wykonana praca& nigdy nie
będzie potrzebna. Najlepiej przecież byłoby, aby sytuacje wyjątkowe nie zdarzały się, a
nasze programy działały zawsze zgodnie z zamierzeniami& Cóż, praca programisty nie
jest usłana różami, więc tak nigdy nie będzie. Nauczmy się więc poprawnie reagować na
wszelkiego typu nieprzewidziane zdarzenia, jakie mogą się przytrafić naszym aplikacjom.
Projektowanie klas wyjątków
C++ umożliwia rzucenie w charakterze wyjątków obiektów dowolnych typów, także tych
wbudowanych. Taka możliwość jest jednak mało pociągająca, jako że pojedyncza liczba
czy napis nie niosą zwykle wystarczającej wiedzy o powstałej sytuacji.
Dlatego też powszechną praktyką jest tworzenie własnych typów (klas) dla obiektów
wyjątków. Takie klasy zawierają w sobie więcej informacji zebranych z miejsca
katastrofy , które mogą być przydatne w rozpoznaniu i rozwiązaniu problemu.
Definiujemy klasę
Co więc powinien zawierać taki obiekt? Najważniejsze jest ustalenie rodzaju błędu oraz
miejsca jego wystąpienia w kodzie. Typowym zestawem danych dla wyjątku może być
zatem:
nazwa pliku z kodem i numer wiersza, w którym rzucono wyjątek. Do tego można
dodać jeszcze datę kompilacji programu, aby rozróżnić jego poszczególne wersje
dane identyfikacyjne błędu - w najprostszej wersji tekstowy komunikat
Nasza klasa wyjątku mogłaby więc wyglądać tak:
#include
class CException
{
private:
// dane wyjątku
std::string m_strNazwaPliku;
unsigned m_uLinijka;
std::string m_strKomunikat;
public:
// konstruktor
CException(const std::string& strNazwaPliku,
unsigned uLinijka,
const std::string& strKomunikat)
: m_strNazwaPliku(strNazwaPliku),
m_uLinijka(uLinijka),
m_strKomunikat(strKomunikat) { }
//-------------------------------------------------------------
// metody dostępowe
std::string NazwaPliku() const { return m_strNazwaPliku; }
unsigned Linijka() const { return m_uLinijka; }
std::string Komunikat() const { return m_strKomunikat; }
};
Dość obszerny konstruktor pozwala na podanie wszystkich danych za jednym zamachem,
w instrukcji throw:
Wyjątki 495
throw CException(__FILE__, __LINE__, "Cos sie stalo");
Dla wygody można sobie nawet zdefiniować odpowiednie makro, jako że __FILE__ i
__LINE__ pojawią się w każdej instrukcji rzucenia wyjątku. Jest to szczególnie przydatne,
jeżeli do wyjątku dołączymy jeszcze inne informacje pochodzące predefiniowanych
symboli preprocesora.
Także konstruktor klasy może dokonywać zbierania jakichś informacji od programu.
Mogą to być np. zrzuty pamięci (ang. memory dumps), czyli obrazy zawartości
kluczowych miejsc pamięci operacyjnej. Takie zaawansowane techniki są aczkolwiek
przydatne tylko w naprawdę dużych programach.
Po złapaniu takiego obiektu możemy pokazać związane z nim dane - na przykład tak:
catch (CException& Wyjatek)
{
std::cout
std::cout
std::cout
std::cout
std::cout
}
Jest to już całkiem zadowalająca informacja o błędzie.
Hierarchia wyjątków
Pojedyncza klasa wyjątku rzadko jest jednak wystarczająca. Wadą takiego skromnego
rozwiązania jest to, że ze względu na charakter danych o sytuacji wyjątkowej, jakie
zawiera obiekt, ograniczamy sobie możliwość obsługi wyjątku. W naszym przypadku
trudno jest podjąć jakiekolwiek działania poza wyświetleniem komunikatu i zamknięciem
programu.
Dla zwiększenia pola manewru możnaby dodać do klasy jakieś pola typu wyliczeniowego,
określające bliżej rodzaj błędu; wówczas w bloku catch pojawiłaby się pewnie jakaś
instrukcja switch.
Jest aczkolwiek praktyczniejsze i bardziej elastyczne wyjście: możemy użyć
dziedziczenia.
[ Pobierz całość w formacie PDF ]
© 2009 ...coś się w niej zmieniło, zmieniło i zmieniało nadal. - Ceske - Sjezdovky .cz. Design downloaded from free website templates