
Vor ein paar Wochen war die Hauptkonferenz in der C ++ - Welt - CPPCON .
An fünf aufeinander folgenden Tagen von 8 bis 22 Uhr gab es Berichte. Programmierer aller Glaubensrichtungen diskutierten über die Zukunft von C ++, vergifteten Fahrrädern und überlegten, wie C ++ einfacher werden könnte.
Überraschenderweise waren viele Berichte der Fehlerbehandlung gewidmet. Mit etablierten Ansätzen können Sie keine maximale Leistung erzielen oder Codeblätter erstellen.
Welche Innovationen erwarten uns in C ++ 2a?
Ein bisschen Theorie
Herkömmlicherweise können alle fehlerhaften Situationen im Programm in zwei große Gruppen unterteilt werden:
- Schwerwiegende Fehler.
- Keine schwerwiegenden oder erwarteten Fehler.
Schwerwiegende Fehler
Nach ihnen macht es keinen Sinn, die Ausführung fortzusetzen.
Dies führt beispielsweise dazu, dass ein Nullzeiger dereferenziert, durch den Speicher geleitet, durch 0 geteilt oder andere Invarianten im Code verletzt werden. Alles, was getan werden muss, wenn sie auftreten, ist, maximale Informationen über das Problem bereitzustellen und das Programm abzuschließen.
In c ++ zu viel Es gibt bereits genügend Möglichkeiten, um das Programm abzuschließen:
Bibliotheken scheinen sogar Daten über Abstürze zu sammeln ( 1 , 2 , 3 ).
Nicht schwerwiegende Fehler
Dies sind Fehler, die von der Programmlogik bereitgestellt werden. Zum Beispiel Fehler beim Arbeiten mit dem Netzwerk, beim Konvertieren einer ungültigen Zeichenfolge in eine Zahl usw. Das Auftreten solcher Fehler im Programm ist in der Reihenfolge der Dinge. Für ihre Verarbeitung gibt es in C ++ mehrere allgemein akzeptierte Taktiken.
Wir werden anhand eines einfachen Beispiels ausführlicher darauf eingehen:
Versuchen wir, eine Funktion void addTwo()
mit verschiedenen Ansätzen zur Fehlerbehandlung zu schreiben.
Die Funktion sollte 2 Zeilen lesen, diese in int
konvertieren und die Summe drucken. E / A-Fehler, Überlauf und Konvertierung in Zahlen müssen behandelt werden. Ich werde uninteressante Implementierungsdetails weglassen. Wir werden 3 Hauptansätze betrachten.
1. Ausnahmen
// // IO std::runtime_error std::string readLine(); // int // std::invalid_argument int parseInt(const std::string& str); // a b // std::overflow_error int safeAdd(int a, int b); void addTwo() { try { std::string aStr = readLine(); std::string bStr = readLine(); int a = parseInt(aStr); int b = parseInt(bStr); std::cout << safeAdd(a, b) << std::endl; } catch(const std::exeption& e) { std::cout << e.what() << std::endl; } }
Mit Ausnahmen in C ++ können Sie Fehler zentral behandeln, ohne unnötige
.
Aber dafür muss man mit einer Menge Probleme bezahlen.
- Der Aufwand für die Behandlung von Ausnahmen ist recht hoch. Sie können nicht oft Ausnahmen auslösen.
- Es ist besser, keine Ausnahmen von Konstruktoren / Destruktoren zu werfen und RAII zu beobachten.
- Durch die Signatur der Funktion ist es unmöglich zu verstehen, welche Ausnahme aus der Funktion herausfliegen kann.
- Die Größe der Binärdatei nimmt aufgrund eines zusätzlichen Ausnahmeunterstützungscodes zu.
2. Rückkehrcodes
Klassischer Ansatz von C. geerbt.
bool readLine(std::string& str); bool parseInt(const std::string& str, int& result); bool safeAdd(int a, int b, int& result); void processError(); void addTwo() { std::string aStr; int ok = readLine(aStr); if (!ok) { processError(); return; } std::string bStr; ok = readLine(bStr); if (!ok) { processError(); return; } int a = 0; ok = parseInt(aStr, a); if (!ok) { processError(); return; } int b = 0; ok = parseInt(bStr, b); if (!ok) { processError(); return; } int result = 0; ok = safeAdd(a, b, result); if (!ok) { processError(); return; } std::cout << result << std::endl; }
Sieht nicht sehr gut aus?
- Sie können den tatsächlichen Wert einer Funktion nicht zurückgeben.
- Es ist sehr leicht zu vergessen, den Fehler zu behandeln (das letzte Mal, als Sie den Rückkehrcode von printf überprüft haben?).
- Sie müssen neben jeder Funktion einen Fehlerbehandlungscode schreiben. Ein solcher Code ist schwerer zu lesen.
Mit C ++ 17 und C ++ 2a werden alle diese Probleme nacheinander behoben.
3. C ++ 17 und Nodiscard
Das nodiscard
in C ++ 17 nodiscard
.
Wenn Sie es vor der Funktionsdeklaration angeben, führt das Fehlen einer Überprüfung des Rückgabewerts zu einer Compilerwarnung.
[[nodiscard]] bool doStuff(); doStuff();
Sie können auch nodiscard
für eine Klasse, Struktur oder Aufzählungsklasse angeben.
In diesem Fall erstreckt sich die Attributaktion auf alle Funktionen, die Werte vom Typ nodiscard
.
enum class [[nodiscard]] ErrorCode { Exists, PermissionDenied }; ErrorCode createDir(); /* ... */ createDir();
Ich werde keinen Code mit nodiscard
.
C ++ 17 std :: optional
In C ++ 17 wurde std::optional<T>
.
Mal sehen, wie der Code jetzt aussieht.
std::optional<std::string> readLine(); std::optional<int> parseInt(const std::string& str); std::optional<int> safeAdd(int a, int b); void addTwo() { std::optional<std::string> aStr = readLine(); std::optional<std::string> bStr = readLine(); if (aStr == std::nullopt || bStr == std::nullopt){ std::cerr << "Some input error" << std::endl; return; } std::optional<int> a = parseInt(*aStr); std::optional<int> b = parseInt(*bStr); if (!a || !b) { std::cerr << "Some parse error" << std::endl; return; } std::optional<int> result = safeAdd(*a, *b); if (!result) { std::cerr << "Integer overflow" << std::endl; return; } std::cout << *result << std::endl; }
Sie können In-Out-Argumente aus Funktionen entfernen, und der Code wird sauberer.
Wir verlieren jedoch Fehlerinformationen. Es wurde unklar, wann und was schief gelaufen ist.
Sie können std::optional
durch std::variant<ResultType, ValueType>
.
Die Bedeutung des Codes ist dieselbe wie bei std::optional
, jedoch umständlicher.
C ++ 2a und std :: erwartet
std::expected<ResultType, ErrorType>
- ein spezieller Vorlagentyp , der wahrscheinlich in den nächsten unvollständigen Standard fällt.
Es hat 2 Parameter.
Wie unterscheidet sich das von der üblichen variant
? Was macht es so besonders?
std::expected
wird eine Monade sein .
Es wird vorgeschlagen, eine Reihe von Operationen auf std::expected
wie auf einer Monade std::expected
: map
, catch_error
, bind
, unwrap
, return
und then
.
Mit diesen Funktionen können Sie Funktionsaufrufe zu einer Kette verketten.
getInt().map([](int i)return i * 2;) .map(integer_divide_by_2) .catch_error([](auto e) return 0; );
Angenommen, wir haben Funktionen mit der Rückgabe von std::expected
.
std::expected<std::string, std::runtime_error> readLine(); std::expected<int, std::runtime_error> parseInt(const std::string& str); std::expected<int, std::runtime_error> safeAdd(int a, int b);
Im Folgenden finden Sie nur Pseudocode, der in keinem modernen Compiler verwendet werden kann.
Sie können versuchen, die Do-Syntax für Aufnahmeoperationen auf Monaden von Haskell auszuleihen. Warum nicht zulassen:
std::expected<int, std::runtime_error> result = do { auto aStr <- readLine(); auto bStr <- readLine(); auto a <- parseInt(aStr); auto b <- parseInt(bStr); return safeAdd(a, b) }
Einige Autoren schlagen diese Syntax vor:
try { auto aStr = try readLine(); auto bStr = try readLine(); auto a = try parseInt(aStr); auto b = try parseInt(bStr); std::cout result << std::endl; return safeAdd(a, b) } catch (const std::runtime_error& err) { std::cerr << err.what() << std::endl; return 0; }
Der Compiler konvertiert einen solchen Codeblock automatisch in eine Folge von Funktionsaufrufen. Wenn die Funktion irgendwann nicht mehr das zurückgibt, was von ihr erwartet wird, wird die Berechnungskette unterbrochen. Ja, und als Fehlertyp können Sie die bereits im Standard vorhandenen Ausnahmetypen verwenden: std::runtime_error
, std::out_of_range
usw.
Wenn Sie die Syntax gut gestalten können, können Sie mit std::expected
einfachen und effizienten Code schreiben.
Fazit
Es gibt keinen idealen Weg, um mit Fehlern umzugehen. Bis vor kurzem gab es in C ++ fast alle möglichen Methoden zur Fehlerbehandlung außer Monaden.
In C ++ 2a werden wahrscheinlich alle möglichen Methoden angezeigt.
Was zum Thema zu lesen und zu sehen ist
- Tatsächlicher Vorschlag .
- Rede über std :: erwartet mit CPPCON .
- Andrei Alexandrescu über std :: erwartet in C ++ Russland .
- Mehr oder weniger aktuelle Diskussion des Vorschlags zu Reddit .