Fehlerbehandlung mit C++
Der Benutzer eines Programms erwartet heute eine erhebliche Robustheit der Programme gegen Fehlbedienungen oder Störungen im Rechner. Um diese Robustheit zu erzeugen, müssen Programme eine Vielzahl von Tests durchführen, um zu jedem Zeitpunkt den korrekten Ablauf sicherzustellen.
Für den Umgang im Programm mit Meldungen über Ablaufprobleme gibt es unterschiedliche Verfahren, die wir aus der Sicht von C++ in diesem Kapitel untersuchen wollen.
In den wenigsten Fällen wird eine Fehlermeldung tatsächlich die Bedeutung eines fehlerhaften Zustandes haben. Zumeist handelt es sich bei der Fehlermeldung um etwas ganz Normales, wie am Ende der Datei angekommen.
Trotzdem bleibt die Frage, ob in allen Fällen, wo es notwendig wäre, der Programmierer die Meldung korrekt auswertet, eventuell Gegenmaßnahmen ergreift und ob dies genügt, um das Programm fortsetzen zu können.
Noch einen ganz anderen Aspekt sollte man berücksichtigen, wenn man über die Fehleranfälligkeit von Programmen spricht. Der Test von unzähligen System- und Bibliotheksaufrufen, die in jedem größeren Programm vorkommen, bläht den Quelltext auf und macht ihn zumindest sehr schwer lesbar. Mit steigender Zeilenanzahl wächst absolut aber wieder das Fehlerrisiko.
Als Autor oder als Dozent macht man sich ab und zu das Leben leicht, indem man einfach das Wesentliche zeigt und die Fehlerbehandlung weg läßt. Vielleicht wirkt diese Vorgehensweise auch als schlechtes Vorbild.
|
Fehlerbehandlung mit C++ Seite 261 |
Fehlerbehandlungen in C und C++
Alle Betriebssystemaufrufe und die meisten Aufrufe der Standardbiblio- thek, die eine Ein- oder Ausgabe durchführen, liefern eine Statusinformation zurück. Die Frage ist, wie man diese Statusinformation im Programm handhabt.
Ignorieren einer Statusmeldung
Ein einfaches, aber leider zu häufig vorkommendes Verfahren ist das schlichte Ignorieren eines möglichen Fehlerzustandes.
Rahmen1
Das erste Beispiel (Bild 18-1) zeigt die typische Situation. Wie jede andere Funktion, die eine Dienstleistung des Betriebssystems in Anspruch nimmt, meldet "printf()" das Ergebnis seiner Arbeit zurück. Hat die Ausgabe funktioniert, gibt "printf()" die Anzahl der ausgegebenen Zeichen zurück. Ist das Ergebnis 0, konnte nicht geschrieben werden.
|
Seite 262 Einsteigerseminar C++ |
Dies ist bei einer Ausgabe auf den Bildschirm unwahrscheinlich, aber es besteht ja immer noch die Möglichkeit, daß die Standardausgabe beim Aufruf umgelenkt wurde. Dann sollte das Schreiben auf eine volle Diskette erkannt werden.
Der erste "printf()"-Aufruf wird im Beispiel noch abgefragt, ohne allerdings das Ergebnis auf 0 zu testen. Statt dessen wird der Rückgabewert mit einem häufig vorkommenden "printf()"-Aufruf ohne Abholen des Ergebnisses ausgegeben.
Statusabfrage
In manchen Fällen kommt in einer einzigen Rückgabe sowohl ein Wert als auch eine Statusinformation zurück. Ein Beispiel ist die Funktion "getchar()".
Rahmen3
In einem "int"-Ergebnis kommt im Normalfall ein Zeichen im niederwertigen Teil zurück. Der höherwertige Teil des Ergebnisses ist dabei 0. Meldet das Betriebssystem einen speziellen Wert (EOF) zurück, wird dies als Dateiende gewertet (Bild 18-2).
|
Fehlerbehandlung mit C++ Seite 263 |
Im Programm ist mit "putchar()" wieder ein schlechtes Beispiel gegeben. "Putchar()" wird nicht geprüft. "Putchar()" liefert entweder das gerade ausgegebene Zeichen oder im Fehlerfall ebenfalls EOF zurück.
Das Nichtabholen eines Ergebnisses ist eine bewußte Designentscheidung in C, um dem Programmierer alle Freiheiten zu lassen.
Wollte der Programmierer nach dem Erkennen von EOF genau herausfinden, welche Fehlerart aufgetreten ist, müsste er mit Makros wie "feof()" die Ursache genauer bestimmen.
Die Konstante, die für EOF benutzt wird, ist -1. Die binäre Darstellung von -1 ist ein Bitmuster mit lauter Einsen. Auch der höherwertige Statusteil besteht damit nicht mehr aus einer 0 und unterscheidet damit ein mögliches Zeichen, in dem ebenfalls alle 8 Bits auf 1 stehen.
Eine Klasse für sich bilden Funktionen, die normalerweise einen Zeiger zurückliefern. Im Fehlerfall wird hier ein spezieller geliefert: die NULL. Hinter NULL verbirgt sich tatsächlich der Wert 0. Der Compiler wird unter allen Betriebssystemen, oder wie es heute häufig heißt, auf allen Plattformen, sicherstellen, daß diese Adresse ungültig ist. In den besprochenen Beispielen war es oft die Funktion "malloc()", die diesen Spielregeln genügte.
Eine andere Form der Statusmeldung vermeidet die Funktionsrückgabe. Insbesondere bei Betriebssystemaufrufen kann man oft einen Zeiger auf eine Statusvariable übergeben, die nach der Rückkehr den Operationsstatus enthält.
Asynchrone Signale
Eine weitere Möglichkeit sind asynchrone Signale. Ein externes Ereignis tritt zu einem Zeitpunkt auf, der keinen Bezug zum Ablauf des Programms hat, also asynchron zum Ablauf. Technisch sind solche Meldungen durch Unterbrechungen realisiert. Unterbrechungen sind Programmumschaltungen, die ausschließlich über Hardware erfolgen.
|
Seite 264 Einsteigerseminar C++ |
Innerhalb der Bedienroutine einer Unterbrechung wird das Betriebssystem über das Auftreten des Ereignisses informiert. Das Betriebssystem wird nach dem Verlassen der Bedienroutine das betroffene Programm informieren. Dies ist dann das Signal vom Betriebssystem an ein Programm.
Rahmen5
|
Fehlerbehandlung mit C++ Seite 265 |
Der Programmierer muß hier mit dem Betriebssystem zusammen die Voraussetzungen für eine Behandlung bereitstellen. Die Behandlungsfunktionen sind eigenständig und werden im Programm nicht genutzt. Sie dienen nur zur Abarbeitung von Signalen.
Beispiele für asynchrone Ereignisse und damit verbundene Signale sind vielfältig. So kann ein Anwender zu irgendeinem Zeitpunkt versuchen, das Programm abzubrechen. Die Abbruchanforderung kann zu einem beliebigen Zeitpunkt gegeben werden, auch wenn das Programm gerade nicht auf Eingaben des Benutzers wartet.
Ein anderes Beispiel sind Übertragungsprobleme einer Modem-Strecke. Ist ein Benutzer mit Hilfe einer Modemstrecke an einem UNIX-Rechner angeschlossen, wird der Rechner ein Hardwaresignal erhalten, falls die Modemstrecke zusammenbricht, und damit alle Prozesse, die der Benutzer gestartet hat, abbrechen.
Signalbehandlungen erfordern eigene Unterprogramme des Benutzers, die jedoch vom Betriebssystem aus aufgerufen werden, nicht vom eigentlichen Programm. Solche Funktionen nennt man Signalbehandlungs-Funktionen oder auch Rückruf- (engl. callback) Funktionen (Bild 18-3).
Die Abarbeitung erfordert eine Funktionstabelle und einen Index. Beim Start eines Programms wird in Multitasking-Systemen zur Verwaltung ein Programmleitblock angelegt (engl. task control block). Darin enthalten ist eine Sprungtabelle, die mit der Adresse einer allgemeinen Abbruchfunktion vorbesetzt wird.
Diese Tabelle kann der Programmierer zur Laufzeit durch eigene Eintragungen modifizieren. Kommt nun eines der vordefinierten asynchronen Ereignisse, fängt das Betriebssystem es ab und ruft mit der Nummer als Index die Funktion aus der zugehörigen Programmtabelle auf. Dies ist dann die Signalbehandlung.
|
Seite 266 Einsteigerseminar C++ |
Wiederaufsetzpunkte
Eine weitere Technik, die häufig benutzt wird, sind Wiederaufsetzpunkte. Man speichert Informationen über den Programmzustand, wie er an einer bestimmten Stelle im Programm vorliegt. Tritt im weiteren Verlauf ein Fehler auf, kann man von beliebiger Stelle, also auch aus einem tief verschachtelten Funktionsaufruf an diese Stelle zurückkehren.
Dazu existiert in C ein Funktionenpaar "setjmp()" / "longjmp()". Mit der Funktion "setjmp()" wird in eine globale Variable der momentane Programmzustand gespeichert. Die Zustandsinformationen umfassen mindestens einige Register, den gewünschten Zustand des Befehlszeigers und den Stack-Zustand.
Mit diesem Funktionenpaar realisiert man nicht-lokale Sprünge.
Im Zusammenhang mit den Dienstleistungen der Sprache C++ funktioniert dies jedoch nur sehr unvollständig, wie das Beispiel dazu zeigt (Bild 18-4).
Verläßt man mit dem unbedingten Sprungbefehl eine Funktion, werden lokale Objekte nicht mit dem Destruktor behandelt, da die C-Funktion natürlich von den Eigenschaften von C++ noch nichts wissen konnte. Der Programmzustand kann damit irreparabel beschädigt sein.
|
Fehlerbehandlung mit C++ Seite 267 |
Rahmen7 (2)
(Fortsetzung nächste Seite)
|
Seite 268 Einsteigerseminar C++ |
Konventionelle Fehlerbehandlung in C++
In C++ ergaben sich in Bezug auf die Fehlerbehandlung neue Probleme. Die beiden Hauptbereiche sind die Speicherverwaltung und die Konstruktoren.
Was soll geschehen, wenn ein Objekt dynamisch angelegt werden soll und kein Speicherplatz mehr vorhanden ist? In den Sprachversionen bis 2.0, die keine strukturierte Fehlerbehandlung kannten, wurde bei einem Fehlschlag des new-Operators ein ungültiger Zeiger zurückgegeben. Natürlich wurde in diesem Fall auch der Konstruktor nicht aufgerufen.
Diese Statusrückgabe entsprach dem in C üblichen Verfahren. Trotzdem gibt es hier ein Randproblem, das so nicht gelöst werden konnte. Was soll geschehen, wenn in einem Konstruktor eines globalen Objekts ein Fehler auftritt? Die Konstruktoren der globalen Variablen laufen, bevor die main()-Funktion gerufen wird. An wen soll hier eine Meldung abgesetzt werden?
|
Fehlerbehandlung mit C++ Seite 269 |
Objekte und Zustandseigenschaften
Die übliche Lösung dafür war, daß Objekte eine Statusvariable mitgeführt haben. Sollte ein Fehler aufgetreten sein, dann wird das Objekt in einen inaktiven Zustand gesetzt und jede weitere Operation verweigert. Ein Beispiel für dieses Verhalten sind die Ein- und Ausgabeobjekte "cin", "cout" und "cerr", die nach einem Fehler explizit wieder freigegeben werden müssen.
Im Beispiel (Bild 18-5) soll eine Zahl eingelesen werden. Geben wir statt einer Zahl einen Text ein, kann er nicht auf "int" konvertiert werden; wir haben einen Fehler gemacht.
Mit den bekannten Verfahren (not-Operator, Typkonvertierung und Statuslese-Funktion) kann man sich über den Betriebszustand des E/A-Objektes informieren. Hier lesen wir den Betriebszustand in die Variable "error" ein und testen die einzelnen Bits.
Im Fehlerfalle wird der Betriebszustand mit der Methode "clear(0)" zurückgesetzt und mit "sync()" auch die Synchronisation mit dem zugrunde liegenden Standardkanal herbeigeführt. Danach erst kann das Objekt wieder zur Eingabe benutzt werden.
|
Seite 270 Einsteigerseminar C++ |
Rahmen9(Fortsetzung auf der nächsten Seite)
|
Fehlerbehandlung mit C++ Seite 271 |
Rahmen9 (2)
Fehlerhandler für die Speicherverwaltung
Die Speicherverwaltung ist neben der E/A ebenfalls traditionell ein Bereich, dessen Fehlerbehandlung nicht einfach ist. Schließlich braucht das Programm den angeforderten Speicher. Ob eine weitere Bearbeitung überhaupt möglich ist, wenn die Anforderung fehlschlägt, mag vom einzelnen Fall abhängen.
In den bisher behandelten Beispielen zu "new" wurde entweder auf eine Fehlerkontrolle verzichtet oder die Rückgabe auf die ungültige Adresse NULL untersucht. Der Grund, warum man in vielen Fällen beruhigt auf eine Fehlerüberprüfung bei einer einzelnen Verwendung des "new"-Operators verzichten kann, liegt in speziellen Dienstleistungen der Laufzeitumgebung.
Für die Speicherverwaltung wurde die Möglichkeit geschaffen, eine eigene Fehlerfunktion zu installieren. Damit wurde das Verhalten des "new"-Operators geändert. Anstatt im Fehlerfall einen ungültigen Zeiger zu liefern, wurde nach der Installation der Fehlerfunktion bei Speichermangel diese Funktion gerufen.
Der Vorteil dieses Verfahrens ist es, daß man nicht jedesmal wieder eine Abfrage durchführen muß, sondern sich auf die eigene Funktion verlassen kann. In der Behandlung des Speichermangels können noch Aufräumarbeiten durchgeführt werden und anschließend wird üblicherweise das Programm mit "exit(1)" verlassen.
|
Seite 272 Einsteigerseminar C++ |
Das Beispiel (Bild 18-6) zeigt das prinzipielle Vorgehen. Der Programmierer schreibt eine Funktion, die man ohne Parameter aufrufen kann und die auch keinen Rückgabewert besitzt. Diese Funktion darf nicht zurückkehren, sondern muß das Programm nach geeigneten Aufräumarbeiten verlassen.
Die Adresse der Funktion wird mit Hilfe der Funktion "set_new_handler()" der Laufzeitumgebung bekannt gegeben. Die Adresse der bisher installierten Funktion wird dabei zurückgeliefert und kann für eine spätere Verwendung zwischengespeichert werden.
In den ersten Versionen von C++ war es auch möglich, die Adresse der Handlerfunktion einer globalen Variablen zuzuweisen. Dieses Verfahren ist jedoch veraltet und wird im Standard nicht mehr unterstützt.
Die Behandlungsfunktion hat prinzipiell drei Möglichkeiten: a) Sie weiß, wie mehr Speicher zur Verfügung gestellt werden kann. Dann kann sie Speicher bereitstellen und zurückkehren. b) Sie benutzt den noch zu besprechenden Mechanismus der Exceptions und wirft ein Objekt der Klasse "bad_alloc" oder einer davon abgeleiteten Klasse aus. c) Sie beendet das Programm und verläßt es mit exit() oder "abort()".
Nachdem nun die eigene Abbruchfunktion gesetzt wurde kann sie möglicherweise durch eine wiederholte immer größerer Speicherbereiche getestet werden. Die Schleife begrenzt allerdings die Durchlaufanzahl. Unter DOS-Systemen ist der frei verfügbare Speicher insbesondere im "small"-Modell des Compilers sehr begrenzt. Unter UNIX und ähnlichen Betriebssystemen kann jedoch sehr viel mehr dynamischer Speicherplatz zur Verfügung gestellt werden, so daß hier ein Testen der Systemgrenzen nicht sinnvoll ist.
Mit dem Setzen des allgemeinen Abbruch-Handlers kann man sich in den Programmen die einzelne Abfrage bei jeder Speicheranforderung sparen. Der Preis ist allerdings ein totaler Abbruch bei einem Fehler und damit der Verzicht auf eine letzte Möglichkeit, bei einem Fehler einzugreifen. Der Laufzeitgewinn kann dies durchaus rechtfertigen.
|
Fehlerbehandlung mit C++ Seite 273 |
Rahmen29
|
Seite 274 Einsteigerseminar C++ |
Wünschenswert wäre die Mischung aus beiden Eigenschaften. Man sollte auf die Laufzeit verbrauchende Einzelabfrage verzichten können und trotzdem die Kontrolle nicht aufgeben müssen. Diese Vorteile bietet die strukturierte Fehlerbehandlung; technisch ausgedrückt handelt es sich um synchrone Signale.
Strukturierte Fehlerbehandlung in C++
Im Vorschlag an das Standardisierungskomitee [ARM] wurde eine Erweiterung in Form einer strukturierten Fehlerbehandlung (exception handling) vorgeschlagen.
Die Grundidee ist die Verwendung synchroner Signale. In einer Funktion des Programms wird ein Fehler erkannt und ein Signal mit einer speziellen Fehlervariablen generiert. Zur Bearbeitung des Fehlers wird in einer höheren Schicht des Programms eine passende Fehlerbehandlungsroutine geschrieben, die dann die Fehlervariable auswerten kann.
Signale können auch in einer Schicht teilweise behandelt und nur zur weiteren Bearbeitung an die jeweils höhere Schicht weitergereicht werden.
Garantierte Fehlerbehandlung
Bevor wir die Einzelheiten diskutieren, möchte ich den alles entscheidenden Punkt voranstellen.
Entscheidend ist, daß ein Fehler, der im Rahmen der strukturierten Fehlerbehandlung gemeldet wurde, nicht übergangen werden kann. Schreibt der Programmierer keine Fehlerbehandlungsfunktion, wird die Kontrolle an vordefinierte Handler übergeben, die das Programm abbrechen.
Strukturierte Fehlermeldungen müssen somit zwangsweise bearbeitet werden. Dies ist besonders bei der Verwendung von Standardbibliotheken wichtig. Sofern in der Bibliothek Fehler gemeldet werden (z. B. Lese- oder Schreibfehler), würde eine Mißachtung der Meldung zum geordneten Abschluss des Programms führen.
|
Fehlerbehandlung mit C++ Seite 275 |
Neue Schlüsselwörter
Die strukturierte Fehlerbehandlung arbeitet mit den neuen Schlüsselwörtern "try", "throw" und "catch" (Bild 18-7).
Rahmen13
|
Seite 276 Einsteigerseminar C++ |
Ein "try"-Block definiert einen Bereich, für den eine eigene Fehlerbehandlung geschrieben werden soll. Innerhalb des Blockes werden nun Anweisungen ausgeführt und Funktionen mit beliebiger Aufruftiefe gerufen. Entsprechend der dynamischen Funktions- und Blockverschachtelung werden am Stack Rückkehradressen abgelegt und lokale Objekte aufgebaut.
Wird nun innerhalb des Codes, der in einem "try"-Block ausgeführt wird, ein Fehler erkannt, wird eine Meldungsvariable ausgeworfen. Der Auswurf geschieht mit dem Schlüsselwort "throw". "Throw" erhält als Parameter einen typisierten Ausdruck. Bei Bedarf wird mit Hilfe des Ausdrucks eine Fehlervariable dynamisch angelegt und initialisiert. Insbesondere bei Konstanten kann der Compiler auch intern einen Verweis weitergeben, anstatt eine eigene Variable anzulegen. Im Falle eines temporären Objekts wird der passende Konstruktor für das Hilfsobjekt gerufen.
Nach dem Auswurf wird der Aufrufstack bereinigt. Alle bis zu diesem Auswurfpunkt aufgerufen Funktionen (Methoden) werden beendet und die darin angelegten Objekte mit ihrem Destruktor zerstört. Der Stack wird also rückwärts abgearbeitet. Die Probleme der nicht-lokalen Sprünge werden hier vermieden.
Nach dem Aufräumen des Stacks wird der "try"-Block verlassen. Die Anweisungen nach "throw" werden nicht mehr ausgeführt. Es wird erwartet, daß sich hinter dem "try"-Block eine oder mehrere "catch"-Funktionen befinden. "Catch"-Funktionen erwarten einen Parameter. Die "catch"-Anweisungen werden durchsucht, ob eine "catch"-Anweisung gefunden wird, deren Parameter dem Datentyp der ausgeworfenen Variablen entspricht. Die Suchreihenfolge entspricht dabei dem Programmtext.
Wird eine "catch"-Funktion mit passendem Parametertyp gefunden, wird der Fehler als bearbeitet angesehen und die weitere Behandlung der Anwenderfunktion überlassen.
|
Fehlerbehandlung mit C++ Seite 277 |
Rahmen15
|
Seite 278 Einsteigerseminar C++ |
Allgemeine Abbruchfunktion
Wird keine passende "catch"-Funktion gefunden, wird die vordefinierte Funktion "terminate()" aufgerufen. Im Standardfall beendet diese Funktion dann das Programm mit "exit()". Damit wird garantiert, daß kein ausgeworfener Fehler übersehen wird.
Im Beispiel (Bild 18-8) wird innerhalb der gerufenen Funktion eine Exception mit einem "long"-Wert ausgeworfen. Für diesen Fehlertyp steht kein Fänger bereit, so daß der Defaulthandler aufgerufen wird.
Setzen der allgemeinen Abbruchfunktion
Der Defaulthandler "terminate()" kann auch durch den Programmierer ersetzt werden. Damit erhält der Programmierer die Möglichkeit, ein gezieltes Beenden des Programms zu steuern.
Die Behandlung des Abbruches (Bild 18-9) schließt hier alle Dateien und bricht das Programm ab. Dies wäre zwar nicht unbedingt nötig, da offene Dateien am Programmende sowieso geschlossen werden, aber es demonstriert den Sinn einer eigenen Behandlungsfunktion.
Die Funktion "set_new_handler()" hat hier die Funktion, den eigenen Handler zu registrieren. Sie liefert die Adresse des bisher installierten Handlers zurück. Man könnte diese Adresse speichern und nach Bedarf den vorherigen Zustand wieder herstellen.
|
Fehlerbehandlung mit C++ Seite 279 |
.
Rahmen17
|
Seite 280 Einsteigerseminar C++ |
Mehrstufige Fehlerbehandlung
Die Fehlerbehandlung kann auch in mehreren Stufen durch mehrere Handler erfolgen, die in logisch verschachtelten Blöcken definiert werden. Dazu dient in einem Handler, der die Bearbeitung weiterreichen will, der Aufruf von "throw" ohne Parameter. Hier wird die gerade bearbeitete Fehlervariable an einen übergeordneten Handler weitergereicht (Bild 18-10).
Insbesondere bei Software, die nach einem Schichtenmodell aufgebaut ist, kann jede Schicht aus ihrer Sicht den Fehler behandeln und bei Bedarf an die jeweils übergeordnete Schicht weiterreichen.
|
Fehlerbehandlung mit C++ Seite 281 |
|
Seite 282 Einsteigerseminar C++ |
Behandlung des Default-Falles
Als Parametertyp kann das Auslassungssymbol für Schnittstellen verwendet werden. Eine "catch"-Anweisung, deren Parameter aus drei Punkten besteht, fängt beliebige Fehler (Bild 18-11).
Damit kann in einer Kette von "catch"-Funktionen der default-Fall eingeführt werden. Dieser Fall steht immer am Schluß der Fälle, da die "catch"-Funktionen der Reihe nach auf Verwendbarkeit getestet werden.
Speicherverwaltungsfehler bei new
Die Reaktion auf Fehler bei "new" war bei älteren Compilern noch nicht konform zum Standard. Überprüfen Sie hier, ob der von Ihnen verwendete Compiler dem Standard entspricht.
Tritt bei "new" ein Fehler auf, wirft in älteren Implementierungen der "new"-Operator ein "xalloc"-Objekt aus. Nun kann ein Konstruktor nur beschränkt eine eigene Fehlerbehandlung durchführen. In allen Fällen, die er nicht handhaben kann, wird der Fehler von der eigenen Fehlerbehandlung erneut ausgeworfen.
Ein "xalloc"-Objekt erhält mit Hilfe des Konstruktors einen Hinweistext und eine Größenangabe.
|
Fehlerbehandlung mit C++ Seite 283 |
|
Seite 284 Einsteigerseminar C++ |
Im Beispiel (Bild 18-12 / Zeilen 22 bis 24) wurde das interne Verhalten von "new" nachgebildet. Sollte die auskommentierte Zeile, die "new" verwendet, einen Fehler ergeben, dann werden intern die nachfolgend gezeigten Anweisungen ausgeführt.
Bei der Initialisierung globaler Objekte kann nicht in einem "try"-Block erfolgen. Das Programm würde bei einem Fehlerauswurf durch "terminate()" beendet. Geschieht daher bei der Konstruktion globaler Objekte ein Fehler, wird das eigentliche Programm nicht erreicht.
Objekte, die als formale Parameter entstehen, könnten nur gesichert werden, wenn der Methoden- oder Funktionsaufruf in einen "try"-Block gelegt wird.
Lokale Objekte könnten innerhalb eines "try"-Blockes angelegt werden. Hier bestünde dann die Möglichkeit des Fangens des Fehlerobjektes.
Allokierungsfehler im Standard-Entwurf
Der früher verwendete Auswurf eines xalloc-Objektes wurde im Standard in den Auswurf eines leichter lesbaren bad_alloc-Objektes geändert. Die einzige Methode, die der Schreiber einer Behandlungsroutine verwenden kann, heißt what() und liefert einen implementierungsabhängigen Text zurück, der als Fehlercode betrachtet werden kann. Weitere Operationen sind hier nicht möglich.
Mit Hilfe der Funktion "set_terminate()" mit einem Parameterwert NULL kann der Auswurf von "bad_alloc" eingeschaltet werden.
|
Fehlerbehandlung mit C++ Seite 285 |
Rahmen23
(Fortsetzung nächste Seite)
|
Seite 286 Einsteigerseminar C++ |
Liste der möglichen Exceptions
In der Schnittstelle einer Funktion (oder Methode) kann mit "throw()" eine Liste der möglichen Ausnahmetypen angegeben werden. Die Funktion sollte dann nur die angegebenen Typen generieren. Wird innerhalb der Funktion ein Typ generiert, der nicht angegeben wurde, wird die Funktion "unexpected()" gerufen.
|
Fehlerbehandlung mit C++ Seite 287 |
Rahmen25
Eine leere Liste bedeutet dabei, daß die Funktion keine Exceptions weiterreicht. Alle Ausnahmen, die von intern gerufenen Funktionen erzeugt werden, werden intern behandelt.
|
Seite 288 Einsteigerseminar C++ |
Rahmen27
Die Funktion "unexpected()" kann wieder, wie auch schon die Funktion "terminate()", vom Programmierer selbst definiert werden. Mit "set_unexpected(Funktionsadresse)" wird die eigene Funktion bekanntgegeben.
|
Fehlerbehandlung mit C++ Seite 289 |
Für die eigene Behandlungsfunktion gelten die gleichen Spielregeln wie bei "set_terminate()". Das Programm muß in der Funktion beendet werden. Eine Rückkehr oder ein Fehlerauswurf sind nicht zulässig (Bild 18-14).
Im Standard werden die meisten Bibliotheksfunktionen einen eigenen Beschreibungsabschnitt erhalten, der die möglichen, ausgeworfenen Fehler auflistet.
Behandlung von Fehlergruppen
Nicht immer besteht die Notwendigkeit, einen Fehler exakt zu bestimmen. Oft würde es genügen, herauszufinden, ob es sich um einen mathematischen Fehler, eine Speicherallokierungsfehler oder einen Ein- und Ausgabefehler handelt.
Hier wäre eine Gruppenbildung von Vorteil.
Einfache Gruppenbildung über Konstante
Gruppen von Fehlern können über unterschiedliche Mechanismen ausgewertet werden. Zum einen kann eine Aufzählung aller Fehlernamen in einer Enumeration erfolgen. Die "catch"-Anweisung würde eine Variable des Enumerations-Typs fangen. Innerhalb der Funktion müßte man jedoch den eigentlichen Fehlerfall mit Hilfe einer Fallunterscheidung herausfinden.
Aufbau von Fehlerhierarchien
Eine ganz andere Möglichkeit bietet die Vererbung. Ausgehend von einer Fehlerbasisklasse wird eine Vererbungshierarchie erstellt. In einer ""-Anweisung kann nun entweder ein Objekt der Basisklasse oder ein Objekt der abgeleiteten Fehlerklasse gefangen werden.
|
Seite 290 Einsteigerseminar C++ |
Eine allgemeine Fehlerklasse kann allerdings nur für die vom Programmierer definierten Fehlerarten erstellt werden, da in der C++-Bibliothek bereits eigene Fehlerklassen existieren.
Zu jedem guten Programmdesign sollte ein hierarchisch gegliederter Fehlerbaum gehören. Fehlerfänger könnten dann entweder auf einen speziellen Fehler oder aber Fehlergruppen angesetzt werden.
Hinweis auf das nächste Kapitel
Die synchronen Signale oder Exceptions sind eine der zahlreichen Erweiterungen, die im Laufe des Standardisierungsprozesses Eingang in C++ gefunden haben. Die Bedeutung für bessere und sichere Programme kann man nicht hoch genug einschätzen.
Uns bleibt im nächsten Kapitel noch eine weitere Neuerung zu besprechen, die Nutzen aus den Typinformationen zieht, die in polymorphen Objekten abgelegt werden: die Operationen zur Typermittlung.
|
Fehlerbehandlung mit C++ Seite 291 |
|
Seite 292 Einsteigerseminar C++ |