Obsługa wyjątków

beziak

Wyjątek - stan (zdarzenie wyjątkowe, w szczególności błąd), którego wystąpienie zmienia prawidłowy przebieg wykonywania się programu.

Składnia

Kod, który potencjalnie może zgłosić wyjątek umiejscawiamy w bloku try, zaś obsługę tych wyjątków w następujących zaraz po nim blokach catch: ```cpp try { // kod, w którym spodziewamy się rzucenia wyjątku } catch (KlasaWyjatku1& kl1) { // w razie rzucenia wyjątku klasy KlasaWyjatku1 wykonany zostanie ten kod } catch (KlasaWyjatku2& kl2) { // jak wyżej, ale gdy zostanie rzucony wyjątek klasy KlasaWyjatku2 } ```

Zgłaszanie (rzucanie) wyjątków

Do zgłaszania wyjątków służy słowo kluczowe throw ```cpp throw wyjatek; ```

Warto wiedzieć, że nie musimy w ten sposób rzucać obiektów klas. Równie dobrze możemy posłużyć się typem wbudowanym:

 throw (unsigned long) 0xDEADBEEF;

Hierarchia wyjątków

Przesłanką przemawiającą za rzucaniem obiektów klas jest możliwość stworzenia hierarchii wyjątków. Wynika to stąd, że bloki catch są w stanie łapać obiekty swojej klasy lub klasy pochodnej. W celu zilustrowania tej właściwości przedstawmy następujący przykład (nazwy klas bardzo obrazowe):
  • Mamy klasę o nazwie Wypadek:
       class Wypadek
       {
         // Treść nieistotna
       };
      
    
  • Tworzymy klasy o nazwie WypadekKolejowy i WypadekSamochodowy dziedziczące z klasy Wypadek:
       class WypadekKolejowy : public Wypadek
       {
         // ...
       };
       class WypadekSamochodowy : public Wypadek
       {
         // ...
       };
      
    
  • Dodatkowo definiujemy klasę Karambol dziedziczącą z klasy WypadekSamochodowy:
       class Karambol : public WypadekSamochodowy
       {
         // ...
       };
      
    

Przykładowy kod przyjmijmy taki:

 try
 {
  throw Karambol();
 }
 catch (Karambol& w)
 {
  std::cout << "Złapano obiekt >>Karambol<<\n";
 }
 catch (WypadekSamochodowy& w)
 {
  std::cout << "Złapano obiekt >>WypadekSamochodowy<<\n";
 }
 catch (Wypadek& w)
 {
  std::cout << "Złapano obiekt >>Wypadek<<\n";
 }
 catch (...)
 {
  std::cout << "Złapał catch(...)!\n";
 }

W takim wypadku, jeżeli zgłosimy Karambol

 throw Karambol();

wyjątek ten zostanie przechwycony przez blok:

 catch (Karambol& w)
 {
  std::cout << "Złapano obiekt >>Karambol<<\n";
 }

Jeżeli natomiast zgłosimy WypadekSamochodowy, zostanie on przechwycony przez drugi blok.

Powstaje teraz pytanie: co się stanie, jak w bloku try znajdzie się taki kod:

 throw WypadekKolejowy();

Nie ma w naszym kodzie bloku catch odpowiedzialnego za łapanie tego typu obiektów. Wypadek kolejowy jest jednak szczególnym rodzajem wypadku - tak samo u nas WypadekKolejowy jest klasą pochodną od Wypadek. Wyjątek zostanie zatem przechwycony przez blok:

 catch (Wypadek& w)
 {
  std::cout << "Złapano obiekt >>Wypadek<<\n";
 }

Na takiej samej zasadzie, jeżeli usuniemy blok odpowiedzialny za łapanie Karambolu, łapać takie wyjątki będzie blok:

 catch (WypadekSamochodowy& w)
 {
  std::cout << "Złapano obiekt >>WypadekSamochodowy<<\n";
 }

Blok catch(...)

W przykładzie powyżej przemycono blok catch z wielokropkiem. Zadanie takiego bloku to: "cokolwiek by nie leciało, łap to!" - blok taki będzie wychwytywał wszystkie nieprzechwycone wcześniej wyjątki. Przykładowo, jeżeli w naszym bloku try rzucimy obiekt typu char: ```cpp throw 'Q'; ``` wyjątek ten zostanie przechwycony przez ostatni blok catch. Wadą takiego "uniwersalnego" bloku *catch* jest brak możliwości odwołania się do złapanego obiektu wyjątku (nie da się sprawdzić, co tak naprawdę "poleciało").

Kolejność bloków catch

Kolejność łapania wyjątków nie jest dowolna. Próba skompilowania następującego kodu:
 catch (Wypadek& w) { }
 catch (WypadekSamochodowy& w) { }

spowoduje zgłoszenie błędu kompilacji. Zasada definiowania bloków catch jest następująca:
Wyjątki należy przechwytywać zaczynając od najbardziej szczegółowych, a kończąc na najbardziej ogólnych.
Z powyższego wynika zatem, że blok catch(...) musi być zawsze ostatnim.

Uwagi

  • Wyjątki można łapać zarówno poprzez wartość, jak i poprzez referencję, ze wszystkimi tego konsekwencjami. Niektórzy ortodoksyjni programiści C++ uważają łapanie wyjątków przez wartość za *obrzydliwe* ;)
  • Bloki try...catch można zagnieżdżać. Nie stanowi to problemu, aby wewnątrz bloku try znalazła się cała konstrukcja try...catch.
  • Niezłapane wyjątki powodują zakończenie wykonywania programu i wypisanie komunikatu o błędzie.
  • Nie należy rzucać wyjątków z destruktorów! Zignorowanie tego faktu może powodować nieoczekiwane zakończenia wykonywania programu.
  • Nie powinno się wyrzucać wyjątków z bloku catch(). Spowoduje to wywołanie funkcji terminate() i w konsekwencji zakończenie działania programu.
  • Mechanizm wyjątków, jak sama nazwa wskazuje, jest używany do obsługi sytuacji *wyjątkowych*, przez co nie jest specjalnie optymalizowany pod kątem wydajności działania. Nie należy go nadużywać.

7 komentarzy

Jeszcze nie ma, że w bloku catch(...) nie ma możliwości sprawdzenia co poleciało.

Mammoth napisał(a)

I jeszcze te cytaty, za miesiąc ktoś napisze artykuł od nowa i wszystko w komentarzach będzie po prostu bez sensu :-).
O rly? O czymś takim jak 'Historia' słyszał?

najłatwiej to samemu nic nie robić, tylko komentować ;-) I jeszcze te cytaty, za miesiąc ktoś napisze artykuł od nowa i wszystko w komentarzach będzie po prostu bez sensu :-). Tego typu komenty można wysłać przez PW. Poza tym artykuł jest i tak lepszy niż poprzednia wersja.

rzucanie wyjątków przez wartość zamiast przez referencję
Chyba łapanie?

Jeżeli w artykule o wyjątkach ani razu nie pojawił się wyraz "stos", tzn. że taki tekst jest kompletnie bezwartościowy. A niniejszy artykuł zawiera jeszcze bardzo rażące błędy, jak na przykład "Wyjątek - jest to specjalny obiekt". I jeszcze to obrzydliwe rzucanie wyjątków przez wartość zamiast przez referencję - masakra. Nawet szkoda czasu na poprawianie. Całość do kosza.

programik3 napisał(a)

2.Przestrzegaj kolejności sprawdzania wyjątków
WTF?

oj malutko, więc słabiutko