Exception-Handling in OOP
Teil des Abschnitts Objektorientierte Programmierung der C++-Journey von Coddy — Lektion 81 von 104.
Ausnahmebehandlung (Exception Handling) in C++ bietet eine strukturierte Möglichkeit, Laufzeitfehler zu erkennen und darauf zu reagieren. In der objektorientierten Programmierung integrieren sich Exceptions auf natürliche Weise in Klassenhierarchien, was es Ihnen ermöglicht, aussagekräftige Fehlertypen zu erstellen, die Kontext darüber enthalten, was schiefgelaufen ist.
Der grundlegende Mechanismus verwendet drei Schlüsselwörter: try, throw und catch. Code, der fehlschlagen könnte, kommt in einen try-Block, Fehler werden mit throw signalisiert und catch-Blöcke behandeln spezifische Ausnahmetypen:
#include <iostream>
#include <stdexcept>
class BankAccount {
double balance;
public:
BankAccount(double b) : balance(b) {}
void withdraw(double amount) {
if (amount > balance) {
throw std::runtime_error("Insufficient funds");
}
balance -= amount;
}
double getBalance() const { return balance; }
};
int main() {
BankAccount account(100.0);
try {
account.withdraw(150.0);
} catch (const std::runtime_error& e) {
std::cout << "Error: " << e.what() << "\n";
}
std::cout << "Balance: " << account.getBalance() << "\n";
}Die C++-Standardbibliothek bietet eine Hierarchie von Exception-Klassen, die bei std::exception beginnt. Zu den gängigen abgeleiteten Klassen gehören std::runtime_error für Laufzeitprobleme und std::logic_error für Programmierfehler. Alle Standard-Exceptions bieten eine what()-Methode, die eine Fehlermeldung zurückgibt.
Wenn eine Exception geworfen wird, wird der Stack abgebaut (Stack Unwinding) – Destruktoren werden für alle lokalen Objekte aufgerufen, während das Programm nach einem passenden catch-Block sucht. Dies stellt eine ordnungsgemäße Bereinigung sicher, selbst wenn Fehler auftreten, weshalb RAII und Exception-Handling so gut zusammenarbeiten.
Aufgabe
EinfachLassen Sie uns einen sicheren Divisionsrechner bauen, der die Ausnahmebehandlung (Exception Handling) in einem objektorientierten Kontext demonstriert. Sie werden eine Calculator-Klasse erstellen, die Divisionsoperationen durchführt und aussagekräftige Ausnahmen auslöst, wenn etwas schiefgeht, und diese Ausnahmen dann in Ihrem Hauptprogramm elegant behandeln.
Sie werden Ihren Code über drei Dateien organisieren:
Calculator.h: Definieren Sie IhreCalculator-Klasse, die mathematische Operationen mit ordnungsgemäßer Fehlerbehandlung durchführt.Ihr
Calculatorsollte eine Methodedivide(double numerator, double denominator)haben, die das Ergebnis der Division zurückgibt. Wenn der Nenner jedoch Null ist, sollte sie einestd::runtime_errormit der NachrichtDivision by zeroauslösen. Wenn entweder der Zähler oder der Nenner negativ ist, sollte sie einestd::invalid_argumentmit der NachrichtNegative values not allowedauslösen.Fügen Sie außerdem eine Methode
safeSqrt(double value)hinzu, die die Quadratwurzel des Wertes zurückgibt. Wenn der Wert negativ ist, lösen Sie einestd::runtime_errormit der NachrichtCannot compute square root of negative numberaus. Sie müssen<cmath>für diesqrt-Funktion einbinden.Calculator.cpp: Implementieren Sie die in Ihrer Header-Datei deklarierten Methoden. Stellen Sie sicher, dass Sie die erforderlichen Header (<stdexcept>und<cmath>) einbinden und die Bedingungen prüfen, bevor Sie die Operationen ausführen.main.cpp: Lesen Sie drei Eingaben ein (jede in einer separaten Zeile):- Operationstyp: entweder
divideodersqrt - Erste Zahl (double)
- Zweite Zahl (double) — wird nur für die Division verwendet, bei sqrt ignoriert
Erstellen Sie ein
Calculator-Objekt und führen Sie die angeforderte Operation innerhalb eines try-Blocks aus. Wenn die Operation erfolgreich ist, geben SieResult: [value]aus. Wenn eine Ausnahme abgefangen wird, geben SieError: [message]aus, wobei die Nachricht aus derwhat()-Methode der Ausnahme stammt.Geben Sie nach dem try-catch-Block (unabhängig davon, ob eine Ausnahme aufgetreten ist)
Calculation completeaus, um zu demonstrieren, dass das Programm nach der Behandlung von Ausnahmen weiter ausgeführt wird.- Operationstyp: entweder
Zum Beispiel bei den Eingaben divide, 10 und 2:
Result: 5
Calculation completeBei den Eingaben divide, 10 und 0:
Error: Division by zero
Calculation completeBei den Eingaben divide, -5 und 2:
Error: Negative values not allowed
Calculation completeBei den Eingaben sqrt, 16 und 0:
Result: 4
Calculation completeBei den Eingaben sqrt, -9 und 0:
Error: Cannot compute square root of negative number
Calculation completeBeachten Sie, wie das Programm Fehler elegant handhabt, indem es Ausnahmen abfängt und aussagekräftige Nachrichten anzeigt und dann die Ausführung fortsetzt. Dieses Muster hält Ihre Fehlerbehandlung sauber und Ihre Hauptlogik auf den Erfolgsfall (Happy Path) konzentriert.
Spickzettel
Die Ausnahmebehandlung in C++ verwendet drei Schlüsselwörter: try, throw und catch. Code, der fehlschlagen könnte, wird in einen try-Block geschrieben, Fehler werden mit throw signalisiert und catch-Blöcke behandeln spezifische Ausnahmetypen:
try {
// Code, der eine Ausnahme auslösen könnte
account.withdraw(150.0);
} catch (const std::runtime_error& e) {
std::cout << "Error: " << e.what() << "\n";
}Auslösen von Ausnahmen aus Klassenmethoden:
void withdraw(double amount) {
if (amount > balance) {
throw std::runtime_error("Insufficient funds");
}
balance -= amount;
}Die C++-Standardbibliothek bietet Ausnahmeklassen an, die auf std::exception basieren. Zu den gängigen Typen gehören:
std::runtime_error– für Laufzeitproblemestd::logic_error– für Programmierfehlerstd::invalid_argument– für ungültige Argumente
Alle Standardausnahmen bieten eine what()-Methode, die eine Fehlermeldung zurückgibt.
Wenn eine Ausnahme ausgelöst wird, wird der Stack abgebaut (Stack Unwinding) – Destruktoren werden für alle lokalen Objekte aufgerufen, während das Programm nach einem passenden catch-Block sucht, was eine ordnungsgemäße Bereinigung gewährleistet.
Binden Sie <stdexcept> ein, um Standard-Ausnahmeklassen zu verwenden.
Probier es selbst
#include <iostream>
#include <string>
#include "Calculator.h"
using namespace std;
int main() {
// Eingaben lesen
string operation;
double num1, num2;
cin >> operation;
cin >> num1;
cin >> num2;
// Calculator-Objekt erstellen
Calculator calc;
// TODO: Einen try-catch-Block verwenden, um die Operation auszuführen
// - Wenn die Operation "divide" ist, rufe calc.divide(num1, num2) auf
// - Wenn die Operation "sqrt" ist, rufe calc.safeSqrt(num1) auf
// - Bei Erfolg "Result: " gefolgt vom Ergebnis ausgeben
// - Exceptions abfangen und "Error: " gefolgt von der Exception-Meldung ausgeben (verwende what())
// - Nach dem try-catch immer "Calculation complete" ausgeben
return 0;
}
Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.
Alle Lektionen in Objektorientierte Programmierung
1Grundlagen der OOP
Externe DateienC++ Build & KompilierungHeader-Dateien & QuelldateienNamensräume & GültigkeitsbereicheEinführung in OOP in C++Klassen vs. ObjekteDer 'this'-PointerMethoden (Elementfunktionen)Attribute (Datenelemente)Grundlagen zu Ctors & DtorsRückblick - Einfacher Taschenrechner4Klasseneigenschaften
Instanz- vs. statische MemberGetter und SetterConst-MemberfunktionenMutable-SchlüsselwortStatische Methoden und VariablenFriend-Funktionen & KlassenZusammenfassung - Bankkonto-Manager7Vererbung
Grundlagen der VererbungZugriffsstufen bei VererbungAufrufreihenfolge von Ctor & DtorÜberschreiben von MethodenVirtuelle Funktionen & VTableMehrfachvererbungVirtuelle VererbungRückblick - Mitarbeiter-Hierarchie2Speicherverwaltung
Stack vs. Heap SpeicherZeiger und ReferenzenDynamischer Speicher (new/delete)Smart Pointers in C++RAII in C++Rückblick – Dynamischer Array-Manager5Kapselung
Zugriffsspezifizierer in C++Zugriffsspezifizierer im DetailInformation HidingStruct vs. ClassVerschachtelte & innere KlassenRückblick - Studentenverwaltungssystem8Polymorphie
Kompilierzeit- vs. Laufzeit-PolymorphieFunktionsüberladungVirtuelle Funktionen – WiederholungRein virtuelle FunktionenAbstrakte KlassenInterface-Design in C++Dynamic Casting & RTTIZusammenfassung – Formen-Rechner11Fortgeschrittene OOP-Konzepte
Komposition vs. VererbungMixins via CRTPPimpl-IdiomType ErasureEnum-Klassen & starke TypisierungException-Handling in OOPEigene Exception-Hierarchien3Konstruktoren & Destruktoren
StandardkonstruktorParametrisierter KonstruktorKopierkonstruktorVerschiebekonstruktorKonstruktor-InitialisierungslistenDelegierende KonstruktorenDestruktoren im DetailRule of Three / Five / ZeroZusammenfassung - String-Klasse6Operatorüberladung
Einführung in die OperatorüberladungArithmetische OperatorüberladungVergleichsoperatorüberladungStream-OperatorenZuweisungsoperatorüberladungÜberladen der [] und () OperatorenTypumwandlungsoperatorenRückblick - Matrix-Klasse9Templates
FunktionstemplatesKlassentemplatesTemplate-SpezialisierungVariadische TemplatesSFINAE & Type Traits GrundlagenRückblick - Generische Container