Exceptions in der Standardbibliothek

std::exception

In der C++ Standardbibliothek werden eine Reihe von Exceptions verwendet. Hierbei gibt es eine Klasse 'std::exception' von der alle Exceptions abgeleitet sind, die wie folgt definiert ist 1):

namespace std
{
  class exception
  {
    public:
      exception() throw();
      exception(const exception&) throw();
      exception& operator=(const exception&) throw();
      virtual ~exception() throw();
      virtual const char* what() const throw();
  };
}

Was hier auffällt ist das nach jeder Methode mittels 'throw()' spezifiziert wird das diese keine Exception wirft. Damit soll sichergestellt werden das während eine geworfene Exception behandelt wird keine weitere geworfen wird, da dies zu einem sofortigen Beenden des Programms führen würde. Sollte aus einer der Methoden trotzdem eine Exception fliegen wird das Programm ebenfalls beendet. Wie das genau abläuft ist an dieser Stelle noch nicht relevant und werden wir erst später betrachten.

Die wohl wichtigste Methode ist 'what()' welche eine kurze, meist generische Beschreibung des aufgetretenen Fehlers zurück gibt. Diese Methode ist virtuell und kann von abgeleiteten Klassen überschrieben werden, um eigenen Fehlerbeschreibungen zurück zu geben.

Vererbungshierarchie

Die Vererbungshierarchie von std::exception ist wie folgt aufgebaut:

  • std::exception
    • std::bad_exception
    • std::bad_typeid
    • std::ios_base::failure
    • std::logic_error
      • std::domain_error
      • std::invalid_argument
      • std::length_error
      • std::out_of_range
      • std::overflow_error
      • std::range_error
      • std::underflow_error

Um die beiden letzten Klassen von Exceptions, also std::logic_error und std::runtime_error mit ihren Erben verwenden zu können müssen wir den Header stdexcept einbinden.

Im Folgenden betrachten wir kurz die wichtigsten Klassen:

std::bad_alloc

Mit malloc hat man früher immer den Rückgabewert auf NULL überprüfen müssen, um festzustellen ob die Speicheranforderung funktioniert hat. Mit C++ und new hat sich das geändert, da hier bei einer fehlgeschlagenen Allokierung anstatt NULL zurückzugeben eine Exception vom Typ std::bad_alloc geworfen wird.

try
{
  char* mem = new char[size];
 
  // Wenn man hier angelangt hat die Speicheranforderung sicher funktioniert
}
catch(std::bad_alloc& ex)
{
  std::cout << "Speicheranforderung fehlgeschlagen!" << std::endl;
}

Möchte man weiterhin anstatt mit einer Exception, durch die Rückgabe von NULL über eine fehlgeschlagenen Speicheranforderung benachrichtigt werden kann man dies durch die Angabe von std::nothrow als Argument von new erreichen.

char* mem = new(std::nothrow) char[size];
 
if( !mem )
{
  std::cout << "Speicheranforderung fehlgeschlagen!" << std::endl;
}

Sollte der Compiler std::nothrow nicht kennen kann man die Definition mit Hilfe von #include <new> einbinden.

std::bad_cast

Wie der Name schon vermuten lässt wird std::bad_cast bei einem fehlgeschlagenem Cast geworfen. Genauer genommen passiert das nur bei einem dynamic_cast, da dieser der einzige Cast ist, der zur Laufzeit erfolgt. Und auch hier nur bei der Verwendung von Referenzen. Bei der Verwendung von Zeiger wird immer NULL zurück gegeben.

struct Base
{
  virtual ~Base() {} // dynamic_cast ist nur bei polymorphen Klassen möglich
};
 
struct Derived1:
  public Base
{};
 
struct Derived2:
  public Base
{};
 
Derived1 d1;
Base& ref_d1 = d1;
 
try
{
  Derived2& ref_d2 = dynamic_cast<Derived2&>(ref_d1); // Wirft hoffentlich eine Exception...
}
catch(std::bad_cast& ex)
{
  std::cout << "dynamic_cast fehlgeschlagen!" << std::endl;
}

std::runtime_error

Diese Klasse stellt noch eine Besonderheit dar, da sie einen Konstruktor bereit stellt, der einen std::string als Argument erwartet und den angegebenen String als Rückgabewert von std::exception::what() bereitstellt. Damit eignet sie sich gut um eine Exception zu werfen ohne dafür einen neuen Typ zu definieren und dabei trotzdem Informationen zum Fehler bereit zu stellen.

#include <stdexcept>
 
try
{
  throw std::runtime_error("Ganz blöd!");
 
  //...
}
catch(std::exception& ex)
{
  std::cout << ex.what() << std::endl;
}

Eigene Exceptions

Die vordefinierten Exceptions sind alle sehr allgemein gehalten. Für den eigenen Bedarf hat es sich deshalb bewährt von möglichst passenden Exceptions in der Hierarchie eigene abzuleiten und mit weiteren Informationen auszustatten. Wer bereits etwas Erfahrungen mit Exceptions hat sollte sich auch Boost Exception anschauen, da es auch das Hinzufügen von Informationen auf verschiedenen Ebenen erlaubt und so auch erlauben den Fehler leichter zurück zu verfolgen.


Exceptionbehandlung in C++ ← | ↑ Exceptions Start ↑ | → Was passiert wenn eine Exception auftritt?

Diskussion

1)
Quelle: Working Draft, Standard for Programming Language C++. 2005-10-19, Kapitel 18.6.1