C++ Kurs

Standard-Exceptions

Die Themen:

Einleitung
System-Exceptions
logic_error Exception
runtime_error Exceptions, Auslösen von Standard-Exceptions
Übung

Einleitung

Bevor wir uns in den nächsten Lektionen ausführlicher der Standard-Bibliothek widmen, kehren wir nochmals zur Behandlung von Ausnahmezuständen zurück.

Zur Behandlung von Ausnahmezuständen stellt die Sprache C++ bestimmte Standard Exception-Klassen zur Verfügung. Alle Exception-Klassen sind hierbei von der Basisklasse exception abgeleitet und liegen im Namensraum std.

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

Diese Exceptions lassen sich generell in drei Gruppen unterteilen:

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

System-Exceptions

Die in diesem Abschnitt behandelten System-Exceptions sind fester Bestandteil des C++ Standards.

bad_alloc

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

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


...
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 Exception wird durch den dynamic_cast Operator (siehe hier) ausgelöst, wenn die Typkonvertierung einer Referenz fehlschlägt. Beachten Sie bitte, dass nur die fehlerhafte Konvertierung einer Referenz eine bad_cast Exception auslöst. Eine fehlerhafte Konvertierung eines Zeigers liefert, wie bereits erwähnt, einen NULL-Zeiger zurück.

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


class 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 Exception wird durch den typeid Operator ausgelöst, wenn die Typinformationen eines dereferenzierten NULL-Zeigers ermittelt werden soll. Die alleinige Angabe des NULL-Zeigers löst noch keine bad_typeid Exception aus, da ja die Typinformation des Zeigers selbst ermittelt werden kann.

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


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

A *ptrA = new A;
// Dies ergibt einen NULL-Pointer!
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

Die bad_exception Exception kann ausgelöst werden, wenn eine Funktion/Memberfunktion eine Exception auslöst, die nicht in der Exception-Spezifikation der Funktion/Memberfunktion aufgeführt ist.

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


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

In der Funktion Func(...) sind nur Exceptions von Typ int und std::bad_exception zugelassen. Wird eine andere Exception ausgelöst, so wird als Reaktion darauf standardmäßig die Funktion unexpected(...) aufgerufen. Diese Funktion können Sie durch eine eigene Funktion ersetzen, um darin dann eine bad_exception auszulösen.

logic_error Exception

Die logic_error Exceptions sind Exceptions, die u.a. in der Standard-Bibliothek ausgelöst werden können. Sie können diese Exceptions aber auch in Ihren eigenen Programmen auslösen. Sie sollten dabei aber stets den Sinn und Zwecke der Exception beachten. So sollte eine out_of_range Exception auch nur dann ausgelöst werden, wenn eine Bereichsüberschreitung vorliegt.

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

out_of_range

Die out_of_range Exception dient zur Auslösung einer Exception für den Fall, dass ein Wert nicht im erwarteten Bereich liegt. Die Standard-Bibliothek verwendet diese Exception 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.


// 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 Exception wird verwendet, um ein falsches Argument zu signalisieren. Auch diese Exception 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.


// 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 Exception 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 Exception wird innerhalb der Standard-Bibliothek nicht verwendet und dient zum Auslösen von Ausnahmen innerhalb von Domains (funktionelle Anwendungsbereiche).

ios_base::failure Exception

Auch Streams können beim Fehlschlagen von Operationen Exceptions auslösen. Diese 'Erweiterung' der Streams wurde erst zu einem späteren Zeitpunkt in den C++ Standard aufgenommen, als die Streams schon recht häufig eingesetzt wurden. Um mit bestehenden Programmen kompatibel bleiben zu können, lösen Streams defaultmäßig zunächst keine Exceptions aus. Die Auslösung von Exceptions 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 Exception ist in der Header-Datei ios definiert. Sie müssen diese Datei jedoch nicht explizit einbinden, da dies automatisch durch den entsprechenden Stream-Header (z.B. fstream) erfolgt.

Das nachfolgende Beispiel demonstriert den Einsatz von Exceptions beim Verarbeiten einer Datei. Nachdem die Exception 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 Exception ausgelöst. Das gleich trifft auch zu, wenn beim Einlesen der Datei das Dateiende erreicht wird. Innerhalb des Exception-Handlers (catch-Block) können Sie durch Aufruf der Stream-Memberfunktionen eof(), fail() und bad() dann feststellen, welche Art von Fehler aufgetreten ist. Nicht vergessen sollten Sie dabei aber, eine eventuell bereits geöffnet Datei auch wieder zu schließen. Die Memberfunktion is_open() liefert Ihnen den diesbezüglichen Zustand der Datei.


std::ifstream InFile;

// Alten Exception-Status retten
std::ios_base::iostate 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 Exceptions, Auslösen von Standard-Exceptions

runtime_error Exceptions

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

Folgende Exceptions stehen zur Verfügung:

range_error, overflow_error, underflow_error

Alle Exceptions liegen auch im Namensraum std und benötigen die Header-Datei stdexcept.

Auslösen von Standard-Exceptions

Wenn Sie in Ihrer Anwendung eine der erwähnten Exceptions auslösen wollen, so müssen Sie dem Konstruktor der Exception eine Referenz auf ein konstantes string-Objekt übergeben. Sie können jedoch aufgrund der impliziten Konvertierung eines char* in ein string-Objekt auch einen beliebigen C-String hier angeben. Im Beispiel werden die beiden Exceptions underflow_error und overflow_error in Abhängigkeit von einer bestimmten Bedingung ausgelöst. Beachten Sie im Beispiel bitte 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 können Sie dann über die Memberfunktion what() diesen String ausgeben.


void 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());
  }
}

Übung

Übung:

Fangen Sie im nachfolgenden Programm alle auftretenden Exceptions ab. Einige der Exceptions werden von der Sprache C++ ausgelöst und andere innerhalb der Klassen Any bzw Another.


// Uebung zu Exceptions

#include <iostream>
#include <stdexcept>

using std::cout;
using std::endl;

// Globale Konstante für max. Parameterwert des Any ctors
const int ULIMIT=5;

// Klasse löst im ctor eine out_of_range Exception aus
class Any
{
  public:
    Any(int param)
    {
        if ((param<0) || (param>=ULIMIT))
            throw std::out_of_range("ctor-Parameter <0 oder >=5 !");
        cout << "ctor Any\n";
    };
    virtual ~Any()
    { cout << "dtor Any\n"; };
    void Print()
    { cout << "Any Print()\n"; }
};

// Klasse löst in Compute eine underflow_error oder overflow_error Exception aus
// wenn Ergebnis der Addition ausserhalb des short Wertebereichs liegt
class Another
{
  public:
    Another()
    { cout << "ctor Another\n"; };
    virtual ~Another()
    { cout << "dtor Another\n"; };
    short Compute(short op1, short op2)
    {
        short res = op1 + op2;
        if ((op1<0) && (op2<0) && (res>0))
            throw std::underflow_error("Underflow in Another::Compute()");
        if ((op1>0) && (op2>0) && (res<0))
            throw std::overflow_error("Overflow in Another::Compute()");
        return res;
    }
};

// main() Funktion
int main()
{
    // 5 Any Objekte nacheinander anlegen und wieder löschen
    Any *pAny;
    for (int i=0; i<=ULIMIT; i++)
    {
        // Versucht Any Objekt anzulegen
        // Max. ctor Parameterwert muss kleiner ULIMIT sein
        pAny = new Any(i);
        // Typ des derefenzierten pAny Zeigers bestimmen
        cout << "Type of Any: " << typeid(*pAny).name() << endl;
        // Any Objekt wieder löschen
        delete pAny;
    }

    // Neues Any Objekt in Another Objekt konvertieren
    pAny = new Any(4);
    Another& ref = dynamic_cast<Another&>(*pAny);
    ref.Compute(10,10);
    // Any Objekt wieder löschen
    delete pAny;

    // Another Objekt definieren und verschiedene Additionen durchführen
    Another obj;
    cout << "Addition: " << obj.Compute(20000,10000) << endl;
    cout << "Addititon " << obj.Compute(-20000, 30000) << endl;
    cout << "Addititon " << obj.Compute(20000, 30000) << endl;
}

Lösung ansehen!