Programmieren in C++
Kapitel 7: Exceptions - Ausnahmebehandlung

7.1 Einfache Fehlerbehandlung mit Standartparameter

Man kennt vielleicht bereits das Dilemma: In einer Funktion wird ein Wert berechnet, bei dem abhängig von der Benutzereingabe eine Division durch Null auftritt. Jetzt können wir eine Ausgabe auf dem Bildschrim produzieren, dass Programm selbst jedoch läuft weiter, als wäre nichts gewesen.

Exceptions geben uns einen Mechanismus in die Hand, mit dem wir sofort an der Stelle der Fehlerursache an eine Stelle zur Fehlerbehandlung springen können und dabei noch einen Parameter beliebigen Typs mitschicken können. Böse Geister mögen dass als eine Art besseres GoTo bezeichnen. Exceptions sind jedoch ungleich mächtiger!! Zum Beispiel funktioniert der Sprung Funktionsübergreifend, und so ist es auch gedacht: Der Teil der Fehlerbehandlung geschieht immer im main-Teil, während die Ursachen zumeist in Unterfunktionen zu finden sind. (Natürlich muss man die Behandlung nicht im main-Teil machen, wenn sie an anderer Stelle erforderlich ist!)

Eine Ausnahmebahandlung besteht aus drei verschiedenen Teilen. Das Prinzip ist Folgendes: Versuche einmal, eine Funktion auszuführen (try). Wenn in der Funktion ein Fehler auftritt, dann erzeuge eine Ausnahme (throw, man spricht auch von geworfenen Ausnahmen). Diese fange dann im aufrufenden Programmteil ab (catch).

Dabei springt die Funktion THROW an die Stelle CATCH. Hinter throw kann ein Parameter beliebigen Typs stehen (also Zahlen- oder Textkonstanten oder -variablen, Strukturen, Klassen ...). Catch kann so überladen werden, dass es für jeden Typ eine eigene catch-Anweisung gibt.

WICHTIG: Für jeden geworfenen Fehlertyp muss es eine catch-Anweisung geben. Wirft man zum Beispiel einen Fehler vom Typ double, hat aber kein catch für diesen Typ, so produziert im Idealfall der Compiler einen Fehler, im Normalfall stürzt das Programm mit einer Allgemeinen Schutzverletzung ab!

Im übrigen wird das Programm nicht beendet, wenn der Fehler behandelt wurde. Vielmehr wird die Abarbeitung am Ende des letzten catch-Blockes fortgesetzt.

Ein einfaches Beispiel mit einer Funktion, die den Wert von 1/x berechnet. Für x = 0 soll eine Ausnahme geworfen werden, die als Parameter 1 zurückgibt. Falls x = 1 ist, soll eine Ausnahme geworfen werden, die den Text Der Wert ist 1 übergibt.

double f( double x )
{

if( x == 0 ) throw 1;
if( x == 1 ) throw "Der Wert ist 1";
return 1/x;
// Das geht so, weil die return-Anweisung ja nur erreicht wird,
// wenn x weder 0 noch 1 ist!

}

void main( )
{

double x = 1;
try
{ x = f(x); }
catch( double err ) {cout << "Fehlerwert: " << err << endl;}
catch( char* err ) { cout << err << endl; }

}


7.2 Default - Fänger

Um zu vermeiden, das geworfene Fehlertypen nicht gefangen werden, gibt es einen Default-Fänger, der alles abfängt, was noch nicht gefangen wurde. Es ist wichtig, das der Default-Fänger (auch: Fänger-Ellipse) als LETZTES in der catch-Reihe steht, weil er sonst auch Fehler abfängt, für die eigentlich eine eigene Routine existiert!

Hier das Beispiel von oben mit einer Fänger-Ellipse

double f( double x )
{

if( x == 0 ) throw 1;
if( x == 1 ) throw "Der Wert ist 1";
return 1/x;
// Das geht so, weil die return-Anweisung ja nur erreicht wird,
// wenn x weder 0 noch 1 ist!

}

void main( )
{

double x = 1;
try
{ x = f(x); }
catch( double err ) {cout << "Fehlerwert: " << err << endl;}
// Jetzt kommt der Default-Fänger:
catch( ... ) { cout << "Allgemeiner Fehler!" << endl; }

}


7.3 Fehlerstrukturen

Wie bereits in 7.1 erwähnt, gibt es auch die Möglichkeit, Strukturen und Klassen zu werfen. Dabei wird hinter der throw-Anweisung der Konstruktor der Struktur oder Klasse aufgerufen, ohne jedoch ein Objekt zu erzeugen. Das Objekt wird nämlich erst in der catch-Klammer erzeugt, wobei der Konstuktor so aufgerufen wird, wie es hinter throw angegeben wurde. Obiges Beispiel soll wieder ein wenig erweitert werden:

struct fehler{

Fehler( int inr, char* itext );
int nr;
char* text;

};

Fehler::Fehler( int inr, char* itext)
{

nr = inr;
text =
new char[ sizeof( itext ) + 1 ];
text = itext;

}

double f( double x )
{

if( x == 0 ) throw Fehler( 1, "Division by Zero");
return 1/x;

}

void main( )
{

double x = 1;
try
{ x = f(x); }
catch( Fehler err ) {cout << err.text << endl;}

}


7.4 Fehlerklassen-Hirarchie

Zuletzt kann man sich noch den Mechanismus des Ableitens von Klassen zunutze machen, um Fehler hirarchisch zu Gliedern. Man deklariert einfach eine Allgemeine Fehlerklasse ohne Inhalt und leitet davon ebenfalls inhaltslose spezielle Fehlerklassen ab. Das Folgende Beispiel implementiert eine Klasse CPoint (ähnlich CArray aus Kapitel 6), in der anstatt der einfachen Fehlermeldungen aus dem vorherigen Kapitel eben Exceptions verwendet. Es wird eine Allgemeine Fehlerklasse deklariert und davon COutOfDimension und CDimensionMissmatch abgeleitet.

Im main-Teil wird zunächst COutOfDimension gefangen. Trat dieser Fehler auf, ist die Ausnamebehandlung hiermit beendet. Dann wird CPointErrors gefangen. Dieses catch springt auch für ALLE Klassen ein, die von CPointErrors abgeleitet wurden und bis dahin noch nicht gefangen wurden. Zuletzt kommt noch die Fänger-Ellipse, die den Rest fängt, auch wenn in diesem Beispiel kein Rest anfällt.

Danke an Uli für dieses [ Beispiel ]


© Gerhard Zapf
Templates
Sitemap
www.tutorialpage.de
Dateien
Diese Seite wurde seit dem 12.07.01 genau 151 mal abgerufen.
Letzte Änderung: 11.09.2003