Mechanizmy obsługi błędów

  1. Nieprawidłowe:

  • Ignorowanie błędów

  • Kończenie działania programu

  • Komunikaty dla użytkownika

  1. Kłopotliwe:

  • Kod powrotu

  • Zmienna globalna: ostatni błąd

  • Specjalny błędny stan obiektu

  • ANSI: longjmp

  1. Mechanizm wyjątków.

Kod powrotu

Metoda zwraca wartość, która jest interpretowana jako poprawne wykonanie funkcji lub jako błąd. Rezerwacja zwracanej wartości na kod błędu. Funkcja musi być badana, czy nie sygnalizuje wystąpienia błędu (użytkownik może zignorować fakt wystąpienia błędu). Kod obsługi błędów jest wymieszany z innym kodem.

class Container
{
  /* … */
public:
  bool push_back(int i); //zwraca false gdy niepowodzenie
};

Przykład użycia:

Container vec;
if ( !vec.push_back(4) ) { // sprawdzanie, czy nie wystąpił błąd
  // tutaj kod obsługi błędu
}
vec.push_back(8); // ignorowanie potencjalnego błędu

Zmienna globalna

Sygnalizacja wystąpienia błędu przez ustawienie wartości zmiennej globalnej (np. #include <errno.h>). Należy co jakiś czas sprawdzać wartość – użytkownik może zignorować fakt wystąpienia błędu. Kod obsługi błędów jest wymieszany z innym kodem.

extern int errno; //zmienna globalna, przechowuje kody błędów

class File {
  /* … */
  void save(const char* buffer); // ustawia errno, gdy wystąpi błąd
};

Przykład użycia:

File p;
p.save(text);
if ( errno == FILE_NOT_FOUND ) {// sprawdzanie, czy wystąpił określony błąd
  // obsługa błędu
}

Obsługa wyjątków

Jeśli niespodziewana sytuacja pojawi się wewnątrz funkcji, fakt ten zostaje zakomunikowany użytkownikowi przy pomocy specjalnej instrukcji throw. Program przechodzi z normalnego trybu wykonywania programu do trybu obsługi wyjątków. W trybie tym opuszczane są wszystkie wywołane dotąd funkcje lub bloki programu tak długo, aż zostanie napotkany blok obsługi danego wyjątku catch – zwijanie stosu.

Wyjątki standardowe są obiektami zdefiniowanymi za pomocą klas dziedziczących po klasie std::exception. Atrybuty wyjątku mogą być wykorzystane w celu uzyskania informacji opisującej sytuację wyjątkową. Każda klasa wyjątku reprezentuje jakiś jego konkretny typ.

Bloki try/catch

Obiektowe podejście do obsługi sytuacji wyjątkowych. Kod, który może generować błędy umieszczamy w bloku try. Wyłapywanie wyjątków oraz obsługa konkretnego błędu umieszczona jest w osobnym bloku catch.

try
{
  File* source = new File("code.cpp");
  source->open();
  string content = source->read_to_end();
  ...
}
catch (const std::exception& e)
{
  std::cout << e.what() << std::endl;
}

Wielokrotne bloki catch

Bloki catch mogą wielokrotnie występować dla jednego bloku try. Każdy blok catch obsługuje inny rodzaj wyjątku. Blok try może mieć jeden ogólny blok catch.

try
{
  // kod, który próbujemy wykonać
}
catch (const std::out_of_range& e) { /*...*/ }
catch (const std::exception& e) { /*...*/ }
catch (...) { /*...*/ }

Bloki catch

Bloki catch przeszukiwane są zgodnie z porządkiem ich wystąpienia w programie. Wykonane zostają instrukcje tylko z pierwszego bloku, który odpowiada danemu typowi wyjątku.

Sekwencje bloków catch należy tworzyć tak, aby bloki zawierające obsługę bardziej wyspecjalizowanych klas poprzedzały bloki dotyczące bardziej ogólnych klas wyjątków.

Wszystkie bloki programu na drodze od wykrycia wyjątku do jego obsługi zostają porzucone – zwijanie stosu – usuwane są wszystkie lokalne obiekty utworzone w tych blokach.

Mechanizm wyjątków

class Array
{
  int* buffer_;
  int size_;
public:
  class IndexOutOfRange { }; // klasa wyjątku
  int& operator[](int index);
};

int& Array::operator[](int i)
{
  if(i >= 0 && i < size_)
    return buffer_[i];
  else
    throw IndexOutOfRange("opis");
}

void do_something(Array& a)
{
  a[1000] = 13; // operator[] może rzucić wyjątkiem Array::IndexOutOfRange
}

void f(Array& a)
{
  try
  {
    do_something(a);
  }
  catch(const Array:: IndexOutOfRange& a)
  {
    // blok obsługi wyjątku
    // kod obsługi błędu
  }
}

Właściwości mechanizmu wyjątków:

  1. Nie rezerwuje wartości zwracanej

  • Mniej argumentów wywołań funkcji

  • Konstruktory mogą zwracać błędy

  1. Brak obiektów globalnych

  2. Użytkownik nie może zignorować zgłoszonego błędu

  3. Kod obsługi błędów oddzielony od innego kodu

  4. Wymaga wsparcia przez język

  • Mechanizm rzucania wyjątku

    • Inny mechanizm powrotu

    • Korzysta ze zwijania stosu

  • Specyfikacja interfejsu

  • Niewyłapane wyjątki

Klasy reprezentujące błędy

Specjalny typ klas

  • Powinny dziedziczyć po std::exception

  • Konstruktor wyjątku nie może zgłaszać wyjątków

  • Często implementują wzorzec wizytatora

  • Tworzone podczas zgłaszania wyjątku w specjalnym miejscu

Przykładowa klasa błędu podczas konwersji napisu na liczbę:

class InputException : public std::exception
{
  char bad_char_;
public:
  InputException(char c) : bad_char_(c) {}

  InputException(const InputException& e) throw() : bad_char_(e.bad_char_)
  {
  }

  char bad_char() const
  {
    return bad_char_;
  };
};

Standardowe klasy wyjątków

exception

Klasa bazowa wszystkich wyjątków wyrzucanych z biblioteki standardowej C++. Metoda what() zwraca const char* (łańcuch opisujący wyjątek).

logic_error

Klasa pochodna klasy exception. Zgłasza błędy w warstwie logicznej programu, które prawdopodobnie mogą zostać wykryte przez uważne przejrzenie kodu.

runtime_error

Klasa pochodna klasy exception. Zgłasza błędy wykonania, które mogą być wykryte właściwie tylko podczas działania tego programu.

domain_error

Zgłasza naruszenie warunków wstępnych. invalid_argument Informuje, że do funkcji, z której wyrzucono ten wyjątek, przekazano błędny argument.

length_error

Informuje o próbie stworzenia obiektu, którego długość jest większa lub równa npos (największej wartości typu size_t, jaką można zapisać).

out_of_range

Zgłasza informację, że argument jest spoza zakresu.

bad_cast

Wyrzucany w przypadku wykonania nieprawidłowego rzutowania

dynamic_cast

podczas identyfikacji typu w czasie wykonania programu.

bad_typeid

W wyrażeniu typeid(*p)pojawił się pusty (równy nullptr) wskaźnik p.

range_error

Zgłasza naruszenie warunków końcowych.

overflow_error

Zgłasza przepełnienie w wyniku operacji arytmetycznych.

bad_alloc

Zgłasza błąd związany z alokacją pamięci.

Nieobsłużone wyjątki

Nieobsłużony wyjątek powoduje wywołanie funkcji std::terminate(), która z kolei wywołuje funkcję std::abort(). Istnieje możliwość zaimplementowania alternatywnej funkcji zakończenia za pomocą funkcji std::set_terminate().

void myTerminate()
{
  cout << “Uncaught exception!\n”;
  exit(1);
}

void (*oldFunction)();
oldFunction = std::set_terminate(myTerminate);