Fehlerbehandlung in C++: Von try/catch zu modernen Ansätzen
\n\nDer Ausnahmemechanismus in C++ ist ein mächtiges, aber tiefes Verständnis erforderndes Werkzeug. Während die grundlegende Verwendung von try/catch den meisten Entwicklern bekannt ist, umfasst die professionelle Fehlerbehandlung weit mehr: von Ausnahmesicherheitsgarantien bis hin zu alternativen Strategien ohne Ausnahmen. In diesem Artikel werden wir fortgeschrittene Techniken zur Fehlerbehandlung im modernen C++ analysieren.
1. Ausnahmesicherheitsgarantien: Fundament zuverlässigen Codes
\n\nJede Funktion, die eine Ausnahme auslösen kann, muss eine von drei Sicherheitsgarantien bieten:
\n\n- \n
- Basisgarantie (Basic Guarantee): Im Falle einer Ausnahme bleiben die Invarianten des Objekts erhalten, es gibt keine Ressourcenlecks. Das Objekt kann sich in einem unvorhersehbaren, aber gültigen Zustand befinden. \n
- Strenge Garantie (Strong Guarantee): Die Operation wird entweder vollständig ausgeführt, oder der Programmzustand bleibt unverändert (Atomarität). Erreicht durch die Idiom copy-and-swap. \n
- Garantie der Ausnahmefreiheit (Noexcept Guarantee): Die Funktion löst niemals Ausnahmen aus. Kritisch für Destruktoren, Move-Operationen und Operationen, die in Containern verwendet werden. \n
// Beispiel für strenge Garantie durch copy-and-swap\nclass Vector {\n int* data;\n size_t sz, cap;\npublic:\n void push_back(int value) {\n if (sz == cap) {\n // 1. Kopie erstellen\n Vector tmp(*this);\n // 2. Kopie modifizieren (kann Ausnahme auslösen)\n tmp.reserve(cap * 2);\n // 3. Tauschen (noexcept!)\n swap(tmp);\n }\n data[sz++] = value; // noexcept\n }\n \n void swap(Vector& other) noexcept {\n std::swap(data, other.data);\n std::swap(sz, other.sz);\n std::swap(cap, other.cap);\n }\n};\n\n2. RAII als Grundlage der Ressourcenverwaltung
\n\nDie Idiom RAII (Resource Acquisition Is Initialization) ist nicht nur intelligente Zeiger. Es ist ein fundamentales Prinzip, das hilft, Ressourcenlecks bei Ausnahmen zu vermeiden:
\n\n- \n
- Ressource wird im Konstruktor erworben \n
- Wird im Destruktor freigegeben (der standardmäßig
noexceptist) \n - Eine Ausnahme im Destruktor ist UB (undefiniertes Verhalten) \n
// RAII-Wrapper für einen Dateideskriptor\nclass FileDescriptor {\n int fd;\npublic:\n explicit FileDescriptor(const char* path) \n : fd(open(path, O_RDONLY)) {\n if (fd == -1) throw std::runtime_error("Failed to open file");\n }\n \n ~FileDescriptor() noexcept {\n if (fd != -1) close(fd);\n }\n \n // Kopieren verbieten, Verschieben erlauben\n FileDescriptor(FileDescriptor&& other) noexcept : fd(other.fd) {\n other.fd = -1;\n }\n};\n\n3. Moderne Alternativen zu Ausnahmen: std::optional und std::expected
\n\nNicht alle Fehler sollten über Ausnahmen behandelt werden. Für erwartete Fehler (falsche Eingabe, fehlende Daten) sind Wrapper-Typen besser geeignet:
\n\n#include <optional>\n#include <expected> // C++23\n\n// std::optional — für optionale Werte\nstd::optional<int> safe_divide(int a, int b) {\n if (b == 0) return std::nullopt;\n return a / b;\n}\n\n// std::expected — für Fehler mit Informationen (C++23)\nstd::expected<double, std::string> sqrt_checked(double x) {\n if (x < 0) return std::unexpected("Negative input");\n return std::sqrt(x);\n}\n\n// Verwendung\nauto result = safe_divide(10, 0);\nif (result) {\n std::cout << *result;\n} else {\n std::cout << "Division by zero";\n}\n\n4. noexcept: Spezifikation und Optimierungen
\n\nDer Spezifikator noexcept ist nicht nur Dokumentation. Er beeinflusst die Leistung und