C++ Kurs

Standard-Ausnahmen

Die Themen:

Bevor wir uns im nächsten Kapitel ausführlicher der Standard-Bibliothek widmen, kehren wir nochmals zur Behandlung von Ausnahmen zurück.

Zur Behandlung von bestimmten Ausnahmen stellt die Sprache C++ Standard Ausnahme-Klassen zur Verfügung. Alle Ausnahme-Klassen sind von der Basisklasse exception abgeleitet und liegen im Namensraum std.

Das nachfolgende Bild zeigt einen hierarchischen Überblick über die vorhandenen Ausnahme-Klassen:

Ausnahme-Klassen

Ausnahmen lassen sich generell in drei Gruppen unterteilen:

Alle Ausnahme-Klassen enthalten lediglich eine public-Memberfunktion what(), um Informationen über die aufgetretene Ausnahme zu erhalten. what() liefert einen 0-terminierten C-String zurück, dessen Inhalt implementierungsabhängig ist.

System-Ausnahmen

bad_alloc

Die bad_alloc Ausnahme wird ausgelöst, wenn der Operator new den angeforderten Speicher nicht reservieren konnte. D.h. jeder Aufruf des new Operators kann damit prinzipiell eine Ausnahme auslösen.

Die bad_alloc Ausnahme-Klasse ist in der Header-Datei new definiert.

PgmHeader...
try
{
  Any *pObj = new Any(...);  // möglicher bad_alloc
  ...
}
catch (const std::bad_alloc& ex)
{
   std::cout << ex.what() << std::endl;
   ...
}

bad_cast

Die bad_cast Ausnahme wird durch den dynamic_cast Operator ausgelöst, wenn die Typkonvertierung in eine Referenz fehlschlägt. Eine fehlerhafte Konvertierung eines Zeigers liefert, wie bereits an anderer Stelle erwähnt, einen nullptr zurück.

Die bad_alloc Ausnahme-Klasse ist in der Header-Datei typeinfo definiert.

PgmHeaderclass A
{...};
class B
{...};

A *ptrA = new A;
try
{
  B& refB = dynamic_cast<B&>(*ptrA);  // bad_cast Exception
}
catch (const std::bad_cast& ex)
{
  std::cout << ex.what() << std::endl;
}

bad_typeid

Die bad_typeid Ausnahme wird durch den typeid Operator ausgelöst, wenn die Typinformationen eines dereferenzierten NULL-Zeigers bzw. nullptr ermittelt werden soll. Die alleinige Angabe des NULL-Zeigers löst noch keine bad_typeid Ausnahme aus, da ja die Typinformation des Zeigers selbst ermittelt werden kann.

Die bad_typeid Ausnahme-Klasse ist ebenfalls in der Header-Datei typeinfo definiert.

PgmHeaderclass A
{...};
class B
{...};

A *ptrA = new A;
// Dies ergibt einen nullptr!
B *ptrB = dynamic_cast<B*>(ptrA);
try
{
  cout << typeid(*ptrB).name() << endl;
}
catch (const std::bad_typeid& ex)
{
  std::cout << ex.what() << std::endl;
}

bad_exception

Ein bad_exception Ausnahme wird ausgelöst, wenn eine Funktion/Memberfunktion eine Ausnahme auslöst, die nicht in der Ausnahme-Spezifikation der Funktion/Memberfunktion aufgeführt ist.

Die bad_exception Ausnahme-Klasse ist in der Header-Datei exception definiert.

PgmHeadervoid Func() throw (int, std::bad_exception)
{
  ...
  if (...)
    throw "XX";  // bad_exception
}

Die obigen Funktion Func(...) kann laut Ausnahme-Spezifikation nur Ausnahmen von Typ int oder bad_exception auslösen. Wird eine andere Ausnahme ausgelöst, so wird als Reaktion darauf standardmäßig die Funktion unexpected(...) aufgerufen. Diese Funktion kann durch eine eigene Funktion ersetzt werden, um darin dann eine bad_exception auszulösen.

logic_error Exception

logic_error Ausnahmen werden u.a. in der Standard-Bibliothek ausgelöst, können aber auch in eigenen Anwendungen ausgelöst werden. Dabei sollte aber stets der Sinn und Zweck der Exception beachtet werden. So sollte eine out_of_range Ausnahme auch nur dann ausgelöst werden, wenn eine Bereichsverletzung vorliegt.

Alle logic_error Ausnahmen liegen ebenfalls im Namensraum std und benötigen die Header-Datei stdexcept.

out_of_range

Die out_of_range Ausnahme dient zum Auslösen einer Ausnahme für den Fall, dass ein Wert nicht im erwarteten Wertebereich liegt. Die Standard-Bibliothek verwendet diese Ausnahme z.B. innerhalb der Klasse bitset, wenn versucht wird, ein Bit zu setzen/löschen das außerhalb des bitset liegt. So wird im Beispiel unten ein bitset mit 8 Bits definiert und anschließend versucht, das 9. Bit zu setzen.

PgmHeader// out_of_range
std::bitset<8> bits;
try
{
  bits.set(9);  // out_of_range!
}
catch(const std::out_of_range& ex)
{
  std::cout << ex.what() << std::endl;
}

invalid_argument

Diese Ausnahme wird verwendet, um ein falsches Argument zu signalisieren. Auch diese Ausnahme wird von der Klasse bitset verwendet, wenn z.B. ein String in ein bitset umgewandelt werden soll und der String andere Zeichen als '0' oder '1' enthält.

PgmHeader// invalid_argument
try
{
  std::string theBits("101012");
  std::bitset<8> wrongbits(theBits);
}
catch(const std::invalid_argument& ex)
{
  std::cout << ex.what() << std::endl;
}

length_error

Diese Ausnahme signalisiert, dass versucht wird, ein Objekt zu erzeugen welches größer als seine maximal erlaubt Größe ist. Dieser Fall tritt z.B. innerhalb der Standard-Bibliothek dann auf, wenn ein String nach einer Operation mehr als max_size() Zeichen enthalten würde.

domain_error

Diese Ausnahme wird innerhalb der Standard-Bibliothek nicht verwendet und dient zum Auslösen von Ausnahmen innerhalb von Domänen (funktionelle Anwendungsbereiche).

ios_base::failure

Und auch Streams können beim Fehlschlagen von Operationen Ausnahmen auslösen. Diese zusätzliche Funktionalität der Streams wurde erst zu einem späteren Zeitpunkt in den C++ Standard aufgenommen, als Streams schon recht häufig eingesetzt wurden. Um mit bestehenden Anwendungen kompatibel bleiben zu können, lösen Streams defaultmäßig zunächst keine Ausnahmen aus. Das Auslösen von Ausnahmen durch Streams muss explizit durch Aufruf der Stream-Memberfunktion exceptions(...) freigegeben werden. Als Parameter erhält exceptions(...) eines oder mehrere der nachfolgenden Flags:

Flag Bedeutung
std::ios::failbit Beim Einlesen konnten die erwarteten Zeichen nicht eingelesen werden oder bei der Ausgabe konnte die Zeichen nicht geschrieben werden.
std::ios::eofbit Beim Einlesen wurde das Dateiende erreicht.
std::ios::badbit Der Inhalt des Ein- bzw. Ausgabestreams ist nicht mehr konsistent.

Die ios_base::failure Ausnahme ist in der Header-Datei ios definiert. Diese Datei muss jedoch nicht explizit eingebunden werden, da dies automatisch durch den entsprechenden Stream-Header (z.B. fstream) erfolgt.

Das nachfolgende Beispiel demonstriert den Einsatz von Ausnahmen beim Verarbeiten einer Datei. Nach dem die Ausnahmen durch den Aufruf der Memberfunktion exceptions(...) freigegeben wurden, wird innerhalb eines try-Blocks die Datei zunächst geöffnet und danach in einer Endlos-Schleife deren Inhalt zeilenweise eingelesen. Tritt jetzt beim Öffnen der Datei ein Fehler auf, so wird eine ios_base::failure Ausnahme ausgelöst. Das gleich trifft auch zu, wenn beim Einlesen der Datei das Dateiende erreicht wird. Innerhalb des Exception-Handlers (catch-Block) kann dann durch den Aufruf der Stream-Memberfunktionen eof(), fail() und bad() feststellt werden, welche Art von Fehler aufgetreten ist. Nicht vergessen werden sollte dabei aber, eine eventuell bereits geöffnet Datei auch wieder zu schließen. Die Memberfunktion is_open() liefert den diesbezüglichen Status der Datei.

PgmHeaderstd::ifstream InFile;

// Alten Exception-Status retten
auto eOld = InFile.exceptions();
// Exceptions aktvieren
InFile.exceptions(std::ios::failbit | std::ios::eofbit);

// Nun Datei verarbeiten
try
{
  // Datei öffnen
  InFile.open(pszFILE);
  // Datei erfolgreich geöffnet
  for (;;)
  {
    // Alle Zeilen einlesen und ausgeben
    InFile.getline(acLine,nSIZE);
    cout << acLine << endl;
  }
}
// Datei-Exceptions abfangen
catch(std::ios_base::failure& e)
{
  ...
}

runtime_error Ausnahmen

runtime_error Ausnahmen

Die runtime_error Ausnahmen sind Ausnahmen, die aufgrund von Laufzeitberechnungen auftreten können. Diese Ausnahmen werden intern von der Standard-Bibliothek nicht verwendet.

Folgende Ausnahmen stehen zur Verfügung:

range_error, overflow_error, underflow_error

Alle Ausnahmen liegen ebenfalls im Namensraum std und benötigen die Header-Datei stdexcept.

Auslösen von Standard-Ausnahmen

Um in einer Anwendung eine der erwähnten Ausnahmen auszulösen, muss dem Konstruktor der Ausnahme eine Referenz auf ein konstantes string-Objekt übergeben werden. Aufgrund der impliziten Konvertierung eines char* in ein string-Objekt ist auch möglich, einen beliebigen C-String zu übergeben. Im Beispiel werden die beiden Ausnahmen underflow_error und overflow_error in Abhängigkeit von einer bestimmten Bedingung ausgelöst. Beachten Sie im Beispiel die beiden Präprozessor-Makros __FILE__ und __LINE__. Sie fügen den Namen der aktuellen Datei und die aktuelle Zeilennummer in den Text der Fehlermeldung ein. Innerhalb des Exception-Handlers kann dann über die Memberfunktion what() auf diesen String zugegriffen werden.

PgmHeadervoid Func(int param)
{
  if (param>10)
  {
    std::ostringstream os;
    os << "overflow in " << __FILE__ << ", Zeile " << __LINE__;
    throw std::underflow_error(os.str());
  }
  if (param<0)
  {
    std::ostringstream os;
    os << "underflow in " << __FILE__ << ", Zeile " << __LINE__;
    throw std::underflow_error(os.str());
  }
}

ÜbungUnd hier geht's zur Übung.