C++-Programmierung/ Ausnahmebehandlung

Aus Wikibooks
Zur Navigation springen Zur Suche springen
Ausnahmebehandlung
Mirounga leonina.jpg

Zielgruppe:

Anfänger

Lernziel:
Vorgehensweise zur Ausnahmebehandlung in C++.


Übersicht [Bearbeiten]

Sie haben die Möglichkeit aus Ihren Methoden und Funktionen Rückgabewerte an den Aufrufer weiterzugeben. Sie können Rückgabewerte von Funktionen untersuchen und darauf reagieren.

Rückgabewerte ignorieren[Bearbeiten]

Wir verwenden in einem Programm eine Funktion aus einer Programmierbibliothek einer Datenbank, die eine Datenbankverbindung herstellt

db_login

sowie eine weitere Funktion daraus, die eine Abfrage ausführt

db_exec_query

. Diese Funktion gibt zurück, ob die Ausführung funktioniert hat. Es ist aber vorerst unerheblich: Rückgabewerte müssen nicht ausgewertet werden. Verwendet werden diese Funktionen in Ihrer selbsterstellten Funktion

abfrage

.

Nuvola-inspired-terminal.svg
1 unsigned int nAbfragenzaehler(0);
2 void abfrage (const char * psz_Query)
3 {
4   ++nAbfragenzaehler;
5   db_exec_query(psz_Query);
6 }

Da Sie in Ihrem vorigen Programmablauf bereits vorher

db_login

ausgeführt haben, gehen Sie davon aus, dass die Verwendung von

abfrage

immer funktionieren wird. Aber was ist, wenn Sie

abfrage

nun an einer anderen Stelle verwenden? Was passiert wenn

db_exec_query

fehlschlägt?

Rückgabewerte auswerten[Bearbeiten]

Da auch eine Dokumentation der Datenbanksoftware vorliegt, erhalten Sie folgende Erkenntnisse:

  • db_exec_query
    
    ist folgendermassen deklariert:
    int db_exec_query(const char * strQ1)
    
  • Außerdem gibt die Funktion folgende Rückgabewerte:
    • 0 - erfolgreich
    • 1 - kein Login durchgeführt
    • 2 - nicht genügend Speicherplatz

Mit diesen Erkenntnissen können Sie nun ans Werk gehen und

abfrage

ein bisschen mehr Intelligenz und Wiederverwendbarkeit einhauchen:

Nuvola-inspired-terminal.svg
 1 unsigned int nAbfragenzaehler(0);
 2 void abfrage (const char * psz_Query)
 3 {
 4   int nRetQuery =   // Rückgabewert
 5   db_exec_query(psz_Query);
 6 __Fehler_Untersuchung_Start:  // Sprungmarke für Neuuntersuchung
 7   switch (nRetQuery)
 8   {
 9    case 0: // erfolgreich
10      ++nAbfragenzaehler;
11      break;
12    case 1: // kein Login durchgeführt
13      db_login(); // Login durchführen
14      nRetQuery = db_exec_query(psz_Query); // Noch ein Versuch
15      if (nRetQuery == 1) // Immernoch Login-Problem
16        std::cerr << "Kein gültiger Login für Datenbankabfrage: " << psz_Query << std::endl;
17      else
18        goto __Fehler_Untersuchung_Start; // Nun kein Loginproblem mehr
19      break;
20    case 2: // nicht genügend Arbeitsspeicher
21      std::cerr << "Nicht genug Speicherplatz im Datenbanksystem bei Abfrage: " << psz_Query << std::endl;
22      break;
23    default: // unbekannter Rückgabewert
24      std::cerr << "Fehlercode '" << nRetQuery << "' bei Datenbankabfrage: " << psz_Query << std::endl;
25      break;
26   }
27 }

Der Umbau von

abfrage

hat folgende Effekte:

  • Der Rückgabewert von
    db_exec_query
    
    wird in jedem Fall ausgewertet.
  • Bei fehlendem Login wird ein Login ausgeführt und die Abfrage erneut ausgeführt.
  • Bei bestehenden Problemen wird eine Fehlermeldung auf stderror ausgegeben.
  • Bei unbekanntem Rückgabewert wird zumindest dieser Wert ausgegeben. Das kann in einem laufenden System die Fehlersuche deutlich vereinfachen.
  • Wenn Sie
    abfrage
    
    an anderen Stellen in Ihrem Programm einsetzen möchten, können Sie sich darauf verlassen, dass entweder ein Login besteht, durchgeführt wird, oder dass eine Fehlermeldung ausgegeben wird.

Rückgabewerte verwenden[Bearbeiten]

Nach der intensiven Auswertung der Rückgabewerte, ergibt es Sinn auch selber Rückgabewerte zu verwenden. Die Funktion

int main()

beispielsweise ist eine Funktion, die einen Wert vom Typ

int

zurückgibt. Rückgabewerte müssen nicht ausgewertet werden. Wenn eine Methode/Funktion mit Rückgabewert deklariert ist, muss aber an jedem Aussprungpunkt auch ein passender Wert zurückgegeben werden.

Im folgenden Beispiel wird eine Funktion definiert, die Eingabeparameter auswertet und davon abhängig verschiedene Werte zurückgibt.

Nuvola-inspired-terminal.svg
 1 // Funktion die einen Wert vom Typ int zurückgibt
 2 int main( int argc, char *argv[], char *envp[]) 
 3 {
 4 	if (argc > 1)
 5 	{
 6 		std::cout << "Mehr als ein Parameter." << std::endl;
 7 		return 1;
 8 	}
 9 	return 0;
10 }

Ein Aufrufer dieser Funktion bekommt nun einen Rückgabewert von '0', wenn der Wert von

argc

kleiner oder gleich '1' ist. Sonst gibt diese Funktion den Wert '1' zurück.

Merken Sie sich Folgendes:

Rückgabewerte:

  • haben immer einen Typ.
  • müssen nicht ausgewertet werden.
  • müssen an jeder Aussprungstelle angegeben werden, wenn eine Methode/Funktion mit Rückgabetyp deklariert wird.
  • können auch überhaupt nicht verwendet werden, deklarieren Sie dazu
    void
    
    als Rückgabetyp.

Werfen und fangen und weiterwerfen [Bearbeiten]

In dem vorherigen Kapitel haben wir Ihnen eine Möglichkeit gezeigt, wie man auf Fehler reagieren kann: geht etwas schief, wird einfach ein Rückgabewert ungleich

0

zurückgeliefert. In kleinen Programmen mag das noch akzeptabel sein, aber in größeren Bibliotheken wie z. B. Qt oder boost ist das Einarbeiten in die Dokumentation doch recht mühsam, um alle Fehlerwerte korrekt zu behandeln. Außerdem können solche Fehlerwerte einfach übersehen werden, was die Fehlersuche massiv erschwert. C++ bietet einen speziellen Mechanismus, um Fehler zu behandeln: Ausnahmen (engl. exceptions). Dadurch wird die Fehlererkennung von der Fehlerbehandlung entkoppelt. Der resultierende Programmcode wird deutlich übersichtlicher und da Exceptions nicht so einfach ignoriert werden können, erleichtern sie auch die Fehlerdiagnose.

Ausnahmen werfen[Bearbeiten]

Die allgemeine Syntax zum Werfen (von engl. to throw) von Ausnahmen lautet:

Crystal Project Tutorials.png
Syntax:
throw «Objekt»;
«Nicht-C++-Code», »optional«

Theoretisch kann alles geworfen werden, auch

int

s oder andere primitive Datentypen. In der Regel wird jedoch ein Objekt der Klasse

std::exception

aus der Headerdatei

stdexcept

geworfen. Die Konstruktoren übernehmen meist eine Zeichenkette als Fehlermeldung, die später mit der Methode

what()

abgefragt werden kann. Hier ein Beispiel, wie

throw

verwendet werden kann:

Nuvola-inspired-terminal.svg
1 #include <stdexcept>
2 #include <iostream>
3 
4 int flaeche(int breite, int laenge) throw(std::range_error){
5     if ((breite <= 0) || (laenge <= 0))
6         throw std::range_error("Breite oder Länge gleich 0 oder negativ");
7     return (breite * laenge);
8 }

Das

throw

kann auch in der Methodendeklaration stehen und zeigt dann an, welche Art von Ausnahmen beim Aufruf ausgelöst werden können. Wichtig ist, dass die möglichen Ausnahmen in runden Klammern stehen und ggf. durch Kommata getrennt aufgeführt werden.

Ausnahmen fangen[Bearbeiten]

Um auf die Exceptions reagieren zu können (sie zu "fangen") braucht man zwei weitere Schlüsselwörter:

try

und

catch

. Beide leiten Blöcke in geschweiften Klammern ein. Auf jeden

try

-Block folgt mindestens ein

catch

-Block. Im sogenannten

try

-Block stehen Code-Abschnitte, die Ausnahmen auslösen können und im

catch

-Block werden diese "aufgefangen" und behandelt. Nach

catch

stehen die Arten von Exceptions, die in dem jeweiligen Block behandelt werden.

Crystal Project Tutorials.png
Syntax:
try{
    // kritischer Abschnitt
} catch («Exceptionklasse» »bezeichner«){
    // Ausnahmebehandlung
}
«Nicht-C++-Code», »optional«

Hier ist ein Programm, welches die

flaeche()

-Funktion aus dem Beispiel oben verwendet:

Nuvola-inspired-terminal.svg
 1 #include <stdexcept>
 2 #include <ios>
 3 #include <iostream>
 4 
 5 using std::cout;
 6 using std::cerr;
 7 using std::cin;
 8 
 9 int flaeche(int breite, int laenge) throw(range_error);
10 
11 int main(){
12     int a, b;
13     // wenn ein wirklich schwerer Fehler auftritt soll cin eine Exception werfen
14     cin.exceptions(cin.exceptions() | ios_base::badbit);
15     cout << "Breite und Höhe eingeben: ";
16     cin >> a >> b;
17     try {
18         cout << "Flächeninhalt: " << flaeche(a, b) << endl;
19     } catch(const range_error& re){
20         cerr << "Ungültige Eingaben: " << re.what() << endl;
21         return 1;
22     } catch(const ios_base::failure& f){
23         cerr << "Schwerer Eingabefehler in cin:" << endl;
24         cerr << f.what() << endl;
25         return 2;
26     }
27     return 0;
28 }

Ausnahmen weiter werfen[Bearbeiten]

Oft kann es vorkommen, dass die auftretende Exception nicht vollständig behandelt werden kann. Dann ist es nützlich, die aufgetretene Ausnahme weiter zu werfen in der Hoffnung, dass irgendein Aufrufen sich darum kümmert. Die Syntax ist denkbar einfach:

Crystal Project Tutorials.png
Syntax:
try{
    // kritischer Abschnitt
} catch («Exceptionklasse» »bezeichner«){
    // teilweise Ausnahmebehandlung
    // "rethrow", Ausnahme erneut werfen
    throw;
}
«Nicht-C++-Code», »optional«

Die Standard-Fehlerklassen [Bearbeiten]

C++-Standard-Fehlerklassen[Bearbeiten]

Der C++-Standardheader

<stdexcept>

stellt die Basisklasse

std::exception

und zwei Kategorien von Exceptions bereit: logische Fehler und Laufzeitfehler. Die Basisklasse, um Logik-Fehlerklassen zu melden, ist

std::logic_error

, für Laufzeitfehler ist das

std::runtime_error

. Eine genauere Übersicht gibt es auf Websites wie cppreference.com oder cplusplus.com (beide englischsprachig).

std::exception
[Bearbeiten]

std::exception

ist die Basisklasse für Ausnahmen. Die wichtigste (virtuelle) Methode ist

what()

, die ein C-Stil-String (einen Pointer auf ein char) zurück gibt und für die Fehlerdiagnose wichtig ist. Sonst hat diese Klasse eine minimale Schnittstelle: Neben dem Standardkonstruktor gibt es einen Copy-Konstruktor, einen virtuellen Destruktor und einen Zuweisungsoperator.

Laufzeit-Fehlerklassen[Bearbeiten]

  • std::exception
    
    : Basisklasse für alle Exceptions
  • std::runtime_error
    
    : Basisklasse für Laufzeitfehler
  • std::range_error
    
    : signalisiert Bereichsfehler bei internen Berechnungen
  • std::overflow_error
    
    : signalisiert arithmetische Bereichsüberschreitungen
  • std::underflow_error
    
    : signalisiert arithmetische Bereichsunterschreitungen
  • std::system_error
    
    ab C++11, Standardheader
    <system_error>
    
    , signalisiert Fehler vom Betriebssystem oder anderen Low-Level-Anwendungen, hat zusätzlich eine Methode
    code()
    
    , um an das
    std::system_error::code
    
    -Objekt zu gelangen[1]:

Logik-Fehlerklassen[Bearbeiten]

Es gibt eine Reihe von speziellen

std::logic_error

-Klassen. Diese werden bei Fehlern vor der Programmausführung geworfen.

  • std::exception
    
    : Basisklasse für alle Exceptions
  • std::logic_error
    
    : Basisklasse, um logische Fehler (Verletzung von Vorbedingungen, etc.)
  • std::domain_error
    
    : für Bereichsfehler
  • std::invalid_argument
    
    : kennzeichnet ungültige Argumente
  • std::length_error
    
    : für Längenfehler
  • std::out_of_range
    
    : wird geworfen, wenn ein Zugriff außerhalb eines gültigen Bereichs statt findet, z. B. bei
    std::vector
    
    :
    v.at(v.size() + 1);
    
  • std::future_error
    
    : ab C++11, Standardheader
    <future>
    
    , signalisiert Zugriffsfehler auf
    std::future
    
    -Objekte[2]

Fehlerklassen schreiben [Bearbeiten]

Baustelle.svg

Dieses Kapitel ist leider noch nicht vorhanden…


Wenn Sie Lust haben können Sie das Kapitel [[C++-Programmierung/ {{{Name}}}/ {{{Kapitel}}}|{{{Kapitel}}}]] selbst schreiben oder einen Beitrag dazu leisten.

unexpected und terminate [Bearbeiten]

Baustelle.svg

Dieses Kapitel ist leider noch nicht vorhanden…


Wenn Sie Lust haben können Sie das Kapitel [[C++-Programmierung/ {{{Name}}}/ {{{Kapitel}}}|{{{Kapitel}}}]] selbst schreiben oder einen Beitrag dazu leisten.

Zusammenfassung [Bearbeiten]

Baustelle.svg

Zu diesem Abschnitt existiert leider noch keine Zusammenfassung…


Wenn Sie Lust haben können Sie die [[C++-Programmierung/ {{{Name}}}/ Zusammenfassung|Zusammenfassung zum Abschnitt {{{Name}}}]] selbst schreiben oder einen Beitrag dazu leisten.