Fortgeschrittene Fehlerbehandlung in C++: von try/catch zu industriellen Praktiken
Fehlerbehandlung ist einer der Eckpfeiler der industriellen Programmierung. In C++ steht dem Entwickler nicht nur der klassische Mechanismus try/catch zur Verfügung, sondern ein ganzes Arsenal an fortgeschrittenen Techniken: von RAII bis zu Garantien der Ausnahmesicherheit. In diesem Artikel werden wir analysieren, wie man zuverlässigen C++ Code schreibt, der Fehler korrekt behandelt, keine Ressourcen verliert und lesbar bleibt.
1. Grundlagen und Standardfallen von try/catch
Die grundlegende Syntax ist jedem bekannt:
#include <iostream>#include <stdexcept>
void dangerousFunction(int value) { if (value < 0) { throw std::invalid_argument("Value must be non-negative"); } // ... Arbeit mit value}
int main() { try { dangerousFunction(-5); } catch (const std::invalid_argument& e) { std::cerr << "Error: " << e.what() << std::endl; } catch (const std::exception& e) { std::cerr << "Generic error: " << e.what() << std::endl; } catch (...) { std::cerr << "Unknown exception caught" << std::endl; } return 0;}Wichtige Punkte:
- Fangen Sie per Referenz —
catch (const std::exception& e)vermeidet Objekt-Slicing. - Die Reihenfolge der catch-Blöcke ist wichtig: zuerst spezifischere Ausnahmen, dann allgemeine.
- Verwenden Sie
catch (...)nicht übermäßig — es versteckt alle Fehler, einschließlich kritischer (z.B.std::bad_alloc). - Werfen Sie niemals Ausnahmen aus Destruktoren — dies führt zu undefiniertem Verhalten (normalerweise zu einem Programmabsturz).
2. RAII als Alternative zu try/catch für die Ressourcenverwaltung
RAII (Resource Acquisition Is Initialization) ist das zentrale Muster in C++, das Ausnahmen sicher macht. Die Idee: Eine Ressource wird im Konstruktor erworben und im Destruktor freigegeben. Wenn eine Ausnahme auftritt, werden die Destruktoren aller erstellten Objekte automatisch aufgerufen (Stack Unwinding).
#include <fstream>#include <vector>#include <stdexcept>
void processFile(const std::string& filename) { std::ifstream file(filename); // RAII: Datei wird im Konstruktor geöffnet if (!file.is_open()) { throw std::runtime_error("Cannot open file: " + filename); }
std::vector<int> data; int value; while (file >> value) { data.push_back(value); } // Der Destruktor von file schließt die Datei automatisch, // selbst wenn eine Ausnahme auftritt (z.B. bad_alloc bei push_back)}Vorteile von RAII gegenüber manuellem try/catch:
- Keine Notwendigkeit,
finally-Blöcke zu schreiben — Destruktoren garantieren die Bereinigung. - Der Code wird linearer und lesbarer.
- Ausnahmen führen nicht zu Speicherlecks, Verlust von Dateideskriptoren usw.
3. Garantien der Ausnahmesicherheit
In C++ werden üblicherweise drei Sicherheitsstufen für Funktionen unterschieden:
- No-throw guarantee — die Funktion wirft niemals Ausnahmen. Wird mit
noexceptmarkiert. - Strong guarantee — wenn eine Ausnahme auftritt, bleibt der Programmzustand unverändert (atomare Operation).
- Basic guarantee — bei einer Ausnahme treten keine Ressourcenlecks auf, Objekte bleiben in einem konsistenten Zustand, ihr Wert kann sich jedoch ändern.
Beispiel mit strong guarantee (Copy-and-Swap-Idiom):
#include <algorithm> // für std::swap#include <vector>#include <stdexcept>
class SafeVector { std::vector<int> data_;public: void push_back(int value) { // 1. Erstell