Exceptions

Martin Kompf

Die Behandlung von Fehlern und Ausnahmesituationen mittels Exceptions ist ein wichtiger Bestandteil von C++

Für die meisten Softwareentwickler ist es die schlimmste und langweiligste Tätigkeit, ihr Programm auf alle möglichen Fehlersituationen hin abzuklopfen und darauf zu reagieren. Da müssen falsche Benutzereingaben behandelt, auf volle Festplatten reagiert und schlimmstenfalls noch Fehler im Betriebssystem ausgebügelt werden. Dies alles führt vor allem zu unübersichtlichem Code, je nach persönlichem Geschmack produziert der Entwickler tief geschachtelte if-then-else Konstrukte, gotos oder versteckt dies peinlich hinter Makros mit klingendem Namen wie THROW oder UNDO.

Mit C++ gibt es die Möglichkeit, hierfür die im Sprachstandard enthaltenen Exceptions zu verwenden. Dies sei an einem kurzen Beispielprogramm illustriert:

/*  1 */  #include <iostream>
/*  2 */  #include <cstdlib>
/*  3 */  #include <string>
/*  4 */
/*  5 */  using namespace std;
/*  6 */
/*  7 */  class BadValueException {
/*  8 */      public:
/*  9 */          BadValueException( const string &what) : m_what(what) { };
/* 10 */          const string & what() const { return m_what; };
/* 11 */      private:
/* 12 */          string m_what;
/* 13 */  };
/* 14 */
/* 15 */  class StockItem {
/* 16 */      public:
/* 17 */          StockItem() : m_value(0.0) { };
/* 18 */
/* 19 */          virtual void setValue( const char* val);
/* 20 */          virtual double getValue() const { return m_value; };
/* 21 */      private:
/* 22 */          double m_value;
/* 23 */  };
/* 24 */
/* 25 */  void StockItem::setValue( const char* val)
/* 26 */  {
/* 27 */      char *e;
/* 28 */      if (0 == val) throw BadValueException( "NULL value");
/* 29 */      double dval = strtod( val, &e);
/* 30 */      if (isprint(*e)) throw BadValueException( "Numeric Conversion");
/* 31 */      if (dval < 0 || dval > 1E6) throw BadValueException( "Out of range");
/* 32 */      m_value = dval;
/* 33 */  }
/* 34 */
/* 35 */
/* 36 */  int main( int argc, char **argv)
/* 37 */  {
/* 38 */      try {
/* 39 */          StockItem item;
/* 40 */          item.setValue( argv[1]);
/* 41 */          cout <<  "value is " << item.getValue() << endl;
/* 42 */      }
/* 43 */      catch (const BadValueException &e) {
/* 44 */          cerr << "BadValueException: " << e.what() << endl;
/* 45 */      }
/* 46 */      catch (...) {
/* 47 */          cerr << "unknown exception\n";
/* 48 */      }
/* 49 */      return 0;
/* 50 */  }

Jede Exception muß vor ihrer Benutzung deklariert werden. Dies kann im einfachsten Fall durch eine leere class Deklaration erfolgen. Guter Programmierstil ist jedoch, wenn eine Exception zusätzlich noch einen beschreibenden Text zur Fehlersituation transportieren kann. Dieses ist in den Zeilen 7 bis 13 anhand der Klasse BadValueException dargestellt. In realen Anwendungen sollte man außerdem die Möglichkeit zur Übergabe von Modulnamen, Quelltextdatei und Zeilennummer des Fehlerortes vorsehen.

Kern des Beispielprogramms ist die Klasse StockItem (Zeilen 15 bis 23). Die Methode setValue() dieser Klasse (Zeilen 25 bis 33) bekommt eine char* Zeichenkette übergeben und wandelt diese nach double. Falls diese Zeichenkette der NULL-Pointer ist (Zeile 28) oder bei der numerischen Umwandlung ein Fehler auftritt (Zeilen 29 und 30) oder der Wertebereich nicht eingehalten wird (Zeile 31), wird jeweils eine BadValueException mittels throw geworfen.

Im Hauptprogramm (Zeilen 36 bis 50) wird einfach das erste Argument der Kommandozeile an die Methode setValue() übergeben (Zeile 40). In Zeile 41 wird der gewandelte Wert ausgegeben. Wurde jedoch von setValue() eine BadValueException geworfen, so hat das catch Statement in Zeile 43 seinen Auftritt. Die Programmausführung wird dort mit der Ausgabe des Fehlertextes fortgesetzt.

In größeren Softwareprojekten ist die Konzeption des Exception-Modells ein kritischer Punkt, dem entsprechende Aufmerksamkeit geschenkt werden muß. Die Entwickler sollten hier den Aufbau hierarchischer Exception-Bäume durch Klassenvererbung vorsehen. Auch ist es meist sinnvoll, alle Exceptions von der in der C++ Library definierten Klasse exception abzuleiten.