Kurz: C++ meldet Fehler über Ausnahmen (exceptions). Sie wandern die Aufrufkette hoch, bis jemand sie fängt. Dabei werden alle lokalen Objekte ordentlich zerstört (RAII):
Teil des Kurses C++
Ausnahmen
C++ meldet Fehler über Ausnahmen (exceptions). Sie wandern die Aufrufkette hoch, bis jemand sie fängt. Dabei werden alle lokalen Objekte ordentlich zerstört (RAII):
#include <iostream>
#include <stdexcept>
#include <string>
#include <vector>
double teile(double a, double b) {
if (b == 0) throw std::invalid_argument("Division durch 0");
return a / b;
}
int main() {
try {
std::cout << teile(10, 4) << '\n';
std::cout << teile(1, 0) << '\n';
} catch (const std::invalid_argument& e) {
std::cout << "Fehler: " << e.what() << '\n';
}
try {
std::vector<int> v{1, 2, 3};
v.at(10);
} catch (const std::out_of_range& e) {
std::cout << "out_of_range\n";
}
try {
std::stoi("abc");
} catch (const std::exception& e) { // allgemeinster Fänger zuletzt
std::cout << "allgemein: " << e.what() << '\n';
}
return 0;
}Ausgabe:
2.5
Fehler: Division durch 0
out_of_range
allgemein: stoiFange Ausnahmen per const-Referenz. Die wichtigsten Typen stehen in <stdexcept>: std::runtime_error, std::logic_error, std::invalid_argument, std::out_of_range, std::length_error, alle erben von std::exception.
Eigene Ausnahmen
#include <iostream>
#include <stdexcept>
#include <string>
class GuthabenFehler : public std::runtime_error {
public:
GuthabenFehler(double fehlbetrag)
: std::runtime_error("Nicht genug Guthaben"), fehlbetrag_(fehlbetrag) {}
double fehlbetrag() const { return fehlbetrag_; }
private:
double fehlbetrag_;
};
double abheben(double stand, double betrag) {
if (betrag > stand) throw GuthabenFehler(betrag - stand);
return stand - betrag;
}
int main() {
try {
std::cout << abheben(100, 30) << '\n';
std::cout << abheben(100, 130) << '\n';
} catch (const GuthabenFehler& e) {
std::cout << e.what() << ", es fehlen " << e.fehlbetrag() << '\n';
}
return 0;
}Ausgabe:
70
Nicht genug Guthaben, es fehlen 30RAII und Ausnahmen
Weil Destruktoren beim Aufräumen laufen, braucht C++ kein finally:
#include <iostream>
#include <memory>
#include <stdexcept>
struct Verbindung {
Verbindung() { std::cout << "öffne\n"; }
~Verbindung() { std::cout << "schließe\n"; }
};
void arbeite() {
Verbindung v; // wird bei Ausnahme automatisch geschlossen
std::cout << "arbeite ...\n";
throw std::runtime_error("kaputt");
}
int main() {
try { arbeite(); } catch (const std::exception& e) { std::cout << "gefangen: " << e.what() << '\n'; }
return 0;
}Ausgabe:
öffne
arbeite ...
schließe
gefangen: kaputtFehler ohne Ausnahmen: optional, variant, expected
Nicht jeder Fehler ist außergewöhnlich. Wenn “kein Ergebnis” ein normaler Fall ist, passen Typen besser:
#include <iostream>
#include <optional>
#include <string>
#include <variant>
#include <charconv>
std::optional<int> parse(const std::string& s) {
int wert{};
auto [ptr, ec] = std::from_chars(s.data(), s.data() + s.size(), wert);
if (ec == std::errc{} && ptr == s.data() + s.size()) return wert;
return std::nullopt;
}
using Antwort = std::variant<int, std::string>; // entweder int oder string
int main() {
auto a = parse("42"), b = parse("x42");
std::cout << a.value_or(-1) << ' ' << b.value_or(-1) << ' ' << a.has_value() << b.has_value() << '\n';
if (auto z = parse("7")) std::cout << "ok " << *z << '\n';
Antwort r = 12;
Antwort e = std::string("Fehler");
for (const Antwort& x : {r, e}) {
std::visit([](const auto& w) { std::cout << "Wert: " << w << '\n'; }, x);
}
std::cout << std::holds_alternative<int>(r) << std::get<int>(r) << '\n';
return 0;
}Ausgabe:
42 -1 10
ok 7
Wert: 12
Wert: Fehler
112| Werkzeug | Wann |
|---|---|
std::optional<T> | ”vielleicht ein Wert” (Suche, Parsen) |
std::variant<A, B> | genau eine von mehreren Möglichkeiten |
std::expected<T, E> (C++23) | Ergebnis oder Fehler ohne Ausnahme |
| Ausnahmen | seltene, ernsthafte Fehler (Datei fehlt, Netzwerk, ungültiger Zustand) |
Rückgabecode + [[nodiscard]] | Schnittstellen zu C und Echtzeitcode |
assert und Verträge
#include <cassert>
#include <iostream>
int teile(int a, int b) {
assert(b != 0 && "Divisor darf nicht 0 sein"); // Annahme (nur in Debug-Builds)
return a / b;
}
int main() {
std::cout << teile(10, 2) << '\n';
return 0;
}Ausgabe:
5assert prüft Programmierfehler (verletzte Annahmen), nicht Benutzerfehler. Mit -DNDEBUG verschwinden die Prüfungen. static_assert prüft zur Übersetzungszeit.
Regeln
- Ausnahmen für Fehler, die der direkte Aufrufer meist nicht behandeln kann
- Destruktoren dürfen keine Ausnahme nach außen werfen (
noexceptist Standard) - Fange nur, was du behandeln kannst; nicht
catch (...)überall - Gib in Ausnahmen aussagekräftige Meldungen mit
Merke
try { } catch (const Typ& e) { },throw Typ(...); Typen aus<stdexcept>- Eigene Ausnahmen erben von
std::runtime_erroroderstd::exception - RAII ersetzt
finally: Destruktoren räumen bei Ausnahmen auf std::optional,std::variant,std::expectedbilden erwartbare Fehler im Typ abassertfür Annahmen,static_assertfür Prüfungen beim Übersetzen
Übungsaufgabe
Schreibe std::optional<double> wurzel(double x), das für negative Zahlen nullopt liefert, und rufe es mit mehreren Werten auf.
Quiz zur Selbstkontrolle
Wie fängt man Ausnahmen richtig?
- catch (const std::exception& e) (richtig)
- catch (std::exception e)
- catch (*e)
- catch e
Was ersetzt in C++ das finally?
- RAII: Destruktoren räumen automatisch auf (richtig)
- try-with-resources
- defer
- ensure
Wann ist std::optional sinnvoll?
- Wenn “kein Ergebnis” ein normaler Fall ist (richtig)
- Für Ausnahmen
- Für Schleifen
- Für Konstanten
Weiter im Kurs
Zurück: Templates und Concepts
Weiter: Dateien, Streams und Dateisystem
Alle Kapitel: C++ im Überblick