22.3 Hierarchie wyjątków

Za blokiem try można umieścić kilka następujących po sobie bloków catch. Jeśli w takiej sytuacji nastąpi zgłoszenie wyjątku, to podczas poszukiwania odpowiedniej procedury obsługi będzie sprawdzany typ wyjątków deklarowany w nagłówkach przez kolejne bloki catch. Jeśli (po ewentualnej konwersji niejawnej, jak w poprzednim przykładzie) znaleziony zostanie odpowiedni typ, to ta właśnie fraza catch zostanie użyta i na tym obsługa tego wyjątku zakończy się: pozostałe bloki catch zostaną już zignorowane, nawet jeśli jest wśród nich taki o bliższym dopasowaniu typu parametru z typem wyjątku. Konwersje w górę są wykonywane tylko dla argumentów obiektowych. Na przykład program


P172: hier.cpp     Konwersje wyjątków

      1.  #include <iostream>
      2.  using namespace std;
      3.  
      4.  int main() {
      5.      try {
      6.          throw 7;
      7.      }
      8.      catch(double) { cout << "double" << endl; }
      9.      catch(int   ) { cout << "int   " << endl; }
     10.  }

wydrukuje ' int', chociaż gdyby traktować frazę catch jak funkcję, byłoby możliwe wywołanie funkcji oczekującej wartości typu double, jeśli argument jest typu int. Gdyby w linii 6 wysłanym obiektem była liczba '7.' (z kropką), to wybraną frazą catch byłaby ta z parametrem double.

Dla wyjątków typu obiektowego konwersja w górę będzie jednak zastosowana


P173: hierob.cpp     Wyjątki typu obiektowego

      1.  #include <iostream>
      2.  using namespace std;
      3.  
      4.  class A { };
      5.  class B : public A { };
      6.  
      7.  int main() {
      8.      try {
      9.          throw B();
     10.      }
     11.      catch(A) { cout << "A" << endl; }
     12.      catch(B) { cout << "B" << endl; }
     13.  }

jak to widać z wydruku tego programu
    cpp> g++ -pedantic-errors -Wall -o hierob hierob.cpp
    hierob.cpp: In function `int main()':
    hierob.cpp:12: warning: exception of type `B' will be
                        caught by earlier handler for `A'
    cpp> ./hierob
    A
Kompilator wypisał tu ostrzeżenie, bo w tym przykładzie druga fraza catch w ogóle nie jest osiągalna. To samo obowiązuje dla wyjątków wskaźnikowych; program

P174: hier1.cpp     Wyjątki typu obiektowego

      1.  #include <iostream>
      2.  using namespace std;
      3.  
      4.  struct A {
      5.      const char* info() { return "A*"; }
      6.  };
      7.  
      8.  struct B : A {
      9.      const char* info() { return "B*"; }
     10.  };
     11.  
     12.  int main()
     13.  {
     14.      try {
     15.          throw new B;
     16.      }
     17.      catch(A* a) { cout << a->info() << endl; }
     18.      catch(B* b) { cout << b->info() << endl; }
     19.  }

drukuje
    cpp> g++ -pedantic-errors -Wall -o hier1 hier1.cpp
    hier1.cpp: In function `int main()':
    hier1.cpp:18: warning: exception of type `B*' will be
                       caught by earlier handler for `A*'
    cpp> ./hier1
    A*
Tak więc, jeśli klasa  B dziedziczy z  A, wyjątek jest typu B (lub B*), a fraza catch deklaruje typ  A (lub A* lub A&), to wyjątek zostanie wychwycony przez tę frazę, jeśli: Oczywiście, typy wyjątków obsługiwnae przez kolejne frazy catch nie muszą mieć ze sobą nic wspólnego: w każdym przypadku podczas poszukiwania procedury obsługi sprawdzane są kolejno aż do znalezienia typu odpowiadającego typowi wyjątku.

Czasem na samym końcu listy fraz catch umieszcza się taką frazę z trzema kropkami zamiast parametru

       catch(...) {
           // ...
       }
Oznacza ona „wyłap każdy wyjątek, niezależnie od jego typu”.

T.R. Werner, 25 lutego 2017; 22:31