Einleitung
System-Exceptions
logic_error Exception
runtime_error Exceptions, Auslösen von Standard-Exceptions
Übung
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.
Die in diesem Abschnitt behandelten System-Exceptions sind fester Bestandteil des C++ Standards.
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;
...
}
|
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;
}
|
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;
}
|
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.
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.
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; } |
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;
}
|
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.
Diese Exception wird innerhalb der Standard-Bibliothek nicht verwendet und dient zum Auslösen von Ausnahmen innerhalb von Domains (funktionelle Anwendungsbereiche).
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) { ... } |
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.
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());
}
}
|
|
// 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; } |