Advanced Services | Exceptions |
Die Ausnahmebehandlung in C++ kennt im Gegensatz zu Java und C# kein dritten finally-Block. Auch die Hierarchie der Exceptions ist hier sehr übersichtlich, auch wenn in C++11 einige Exceptions hinzugekommen sind. Hier zunächst eine Übersicht über die Standardexceptionklassen in C++
Der Auslösemechanismus mit throw arbeitet mit Objekten beliebigen Datentyps. Im catch-Zweig des try-catch Konstrukts kann man dann dieses Objekt auswerten. Besonders einfach ist dies mit Standarddatentypen. Die folgenden Beispiel arbeiten nach dem gleichen Muster. Es gibt eine Funktion, die un ter bestimmten Bedingungen eine Ausnahme wirft und es gibt eine weitere Funktion, die diese Funktion verwendet und die Ausnahme abfängt.
Die auslösende Funktion
void openfiles(int filecount) { if ( filecount > 64) throw filecount; }
Die abfangende Funktion
void beispiel1() { try { openfiles(70); cout << "success" << endl; } catch(int nr) { cout << "cannot open " << nr << " files, max = 64\n" << endl; } }
Ein Ablauf
Oft werden c-Strings (char*) eingesetzt.
Die auslösende Funktion
void* allocate(int size) { //void* tmp = calloc(1, size); void* tmp = NULL; if( tmp == NULL) throw "memory allocation failed"; return tmp; }
Die abfangende Funktion
void beispiel1() { try { void* buf = allocate(5000); cout << "allocate succeded" << endl; } catch(char* mess) { cout << mess << endl << endl; } }
Ein Ablauf
Etwas eleganter ist es Exceptions der Exceptionhierarchie zu verwenden.
Die auslösende Funktion
void* allocate3a(int size) { //void* tmp = calloc(1, size); void* tmp = NULL; if( tmp == NULL) { std::ostringstream str; // #include <sstream> str << "cannot allocate memory with size = " << size; throw std::bad_alloc( str.str().c_str() ); } return tmp; }
In jeder Exceptionklasse ist gibt es einen parameterlosen Konstruktor und einen der einen klassischen c-String als Parameter hat. Hat man einen eine andere Stringdarstellung muß man konvertieren.
Die abfangende Funktion
void beispiel3a() { try { void* buf = allocate3a(5000); cout << "allocate succeded" << endl; } catch(std::bad_alloc ex) { cout << ex.what() << endl; } }
Statt bad_alloc kann man auch eine Elternklasse verwenden (in diesem Falle ist das std::exception) aber keine Kindklasse.
Ein Ablauf
Eine eigene Exceptionklasse leitet man von einer Standardexceptionklasse ab. Hier ein Beispiel mit dem man über den Konstruktor eine individuelle Fehlermeldung ausgeben kann.
class AllocationException : public exception { private: char* mess; public: AllocationException() : mess("AllocationException: memory allocation failed") {} AllocationException(char* mess) : mess(mess) {} const char *what () const //throw () { return mess; } };
Die auslösende Funktion
void* allocate3b(int size) { //void* tmp = calloc(1, size); void* tmp = NULL; if( tmp == NULL) throw AllocationException("Speicher konnte nicht allokiert werden"); return tmp; }
In jeder Exceptionklasse ist gibt es einen parameterlosen Konstruktor und einen der einen klassischen c-String als Parameter hat. Hat man einen eine andere Stringdarstellung muß man konvertieren.
Die abfangende Funktion
void beispiel3b() { try { void* buf = allocate3b(5000); cout << "allocate succeded" << endl; } catch(AllocationException ex) { cout << ex.what() << endl; } }
Ein Ablauf
Es kann ohne weiteres mehrere catch-Blöcke geben. Stehen die Exceptionklassen nicht in direkter Vererbung ist die Reihenfolge der catch-Blöcke egal, andernfalls muß die Reihenfolge von der Kindklasse zur Elternklasse sein. Als letzten catch-Block kann man immer catch(...) nehmen, damit fängt man unbekannte Fehler ab. Hier eine mögliche Abfolge von catch-Blöcken. Damit kann man für jede Exception eine eigene Fehlerbehandlung erreichen.
void beispiel3c() { try { void* buf = allocate3b(5000); cout << "allocate succeded" << endl; } catch(AllocationException ex) { cout << ex.what() << endl << endl; // action 1 } catch(exception ex) { cout << ex.what() << endl << endl; // action 2 } catch(...) { cout << "unknown exception caught" << endl << endl; // action 3 } }