C++/Obsługa wyjątków

Z Wikibooks, biblioteki wolnych podręczników.
< C++

Wstęp[edytuj]

Wyjątki pozwalają zareagować na sytuacje, w których istnieje ryzyko niewykonania określonego zadania.

Zarys wyjątków[edytuj]

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ć taki jak wartość zwracana przez funkcję. 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". W C++ wyjątki się "rzuca", służy do tego instrukcja throw.

Szkielet obsługi wyjątków[edytuj]

Tam gdzie spodziewamy się wyjątku umieszczamy blok try, w którym umieszczamy "podejrzane" instrukcje. Za tym blokiem muszą (tzn. musi przynajmniej jedna) pojawić się bloki catch. Wygląda to tak:

      //jakaś zwykła funkcja, lub funkcja main
      try  // w instrukcjach poniżej może coś się nie udać
      {
         fun();
         fun2();   //podejrzane funkcje
      }
      catch(std::string obj)
      {
         //tu coś robimy, na przykład piszemy o błędzie
      }

W instrukcji catch umieszczamy typ jakim będzie wyjątek. Rzucić możemy wyjątek typu int, char, std::string i inne, dlatego tu określamy co nas interesuje. Nazwa tego obiektu nie jest konieczna, ale jeżeli chcemy znać wartość musimy ten obiekt nazwać. 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 dobrego catch nawet jeżeli inne nadają się lepiej (podobnie jak z instrukcjami if else). Dotyczy to zwłaszcza klas dziedziczonych. Przykładowo, jeżeli mamy klasę Pies, która dziedziczy z klasy Zwierze, to jeśli pierwszy pojawi się blok:

          catch(Zwierze obj)

to on zostanie użyty do obsługi wyjątku.

Zawsze dobrze jest się zabezpieczyć blokiem

          catch(...)

Taki blok łapie wszystko. Dlatego kompilator nie dopuści by wszystkołapiący catch był przed innymi instrukcjami catch.

Rzucanie wyjątku[edytuj]

Pisząc funkcję możemy stwierdzić że coś poszło nie tak i chcemy zasygnalizować wyjątek. Jak to zrobic przedstawia kod:

         double Dziel(double a, double b) //funkcja zwraca iloraz a / b
         {
             if(b == 0)    {
                 std::string wyjatek = "dzielenie przez zero!"; //przez zero się nie dzieli
                 throw wyjatek;  //rzucamy wyjątek
             }
             return a / b;
         }

Po instrukcji throw umieszczamy obiekt który chcemy rzucić (u nas jest to std::string). W tym miejscu działanie funkcji jest natychmiast przerywane i nasz łańcuch znaków wędruje do bloków catch.

Pełny program ilustrujący wyjątki:

        #include<iostream>
        #include<string>
        #include<cmath>
        using namespace std;
        double Dziel(double, double);
        int main()
        {
            try
            {
                Dziel(10, 0);
            }
            catch(string w)
            {
                cout<<"Wyjatek: "<<w;
            }
            cin.get();
            return 0;
         }
         double Dziel(double a, double b) //funkcja zwraca iloraz a / b
        {
            if (b == 0)    {
                 string wyjatek = "dzielenie przez zero!";
                 throw wyjatek;
            }
            return a / b;
        }

Jak widać, utworzony jest tylko jeden blok catch, a to dlatego że funkcja Dziel rzuca tylko wyjątki typu std::string. Możliwe jest pisanie obok deklaracji funkcji jakie wyjątki może ona rzucać:

       void fun(int) throw(std::string)

zapis ten oznacza, że funkcja fun może zwrócić wyjątek typu std::string. Stanowi to jednak zły zwyczaj i zaleca się jego unikanie.