Ausnahmebehandlung in OOP
Teil des Abschnitts Objektorientierte Programmierung der C++-Journey von Coddy. Lektion 81 von 104.
Exception-Behandlung in C++ bietet eine strukturierte Möglichkeit, Laufzeitfehler zu erkennen und darauf zu reagieren. In der objektorientierten Programmierung fügen sich Exceptions natürlich in Klassenhierarchien ein, sodass du aussagekräftige Fehlertypen erstellen kannst, die Kontext darüber enthalten, was schiefgelaufen ist.
Der grundlegende Mechanismus verwendet drei Schlüsselwörter: try, throw und catch. Code, der möglicherweise fehlschlägt, kommt in einen try-Block, Fehler werden mit throw signalisiert, und catch-Blöcke behandeln bestimmte 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 stellt eine Hierarchie von Ausnahme-Klassen bereit, deren Wurzel std::exception ist. Zu den häufigen abgeleiteten Klassen gehören std::runtime_error für Laufzeitprobleme und std::logic_error für Programmierfehler. Alle Standardausnahmen stellen eine what()-Methode bereit, die eine Fehlermeldung zurückgibt.
Wenn eine exception ausgelöst wird, wird der Stack abgewickelt – Destruktoren werden für alle lokalen Objekte aufgerufen, während das Programm nach einem passenden catch-Block sucht. Dadurch wird eine ordnungsgemäße Bereinigung auch dann sichergestellt, wenn Fehler auftreten. Deshalb funktionieren RAII und die Ausnahmebehandlung so gut zusammen.
Aufgabe
EinfachErstellen wir einen sicheren Divisionsrechner, der die Ausnahmebehandlung in einem objektorientierten Kontext demonstriert. Du erstellst eine Calculator-Klasse, die Divisionsoperationen ausführt und aussagekräftige Ausnahmen auslöst, wenn etwas schiefgeht, und behandelst diese Ausnahmen anschließend in deinem Hauptprogramm auf geeignete Weise.
Du organisierst deinen Code auf drei Dateien verteilt:
Calculator.h: Definiere deineCalculator-Klasse, die mathematische Operationen mit geeigneter Fehlerbehandlung ausführt.Dein
Calculatorsollte eine Methodedivide(double numerator, double denominator)besitzen, 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üge außerdem eine Methode
safeSqrt(double value)hinzu, die die Quadratwurzel des Werts zurückgibt. Wenn der Wert negativ ist, löse einestd::runtime_errormit der NachrichtCannot compute square root of negative numberaus. Du musst<cmath>für die Funktionsqrteinbinden.Calculator.cpp: Implementiere die in deiner Header-Datei deklarierten Methoden. Achte darauf, die erforderlichen Header (<stdexcept>und<cmath>) einzubinden und die Bedingungen vor der Ausführung der Operationen zu prüfen.main.cpp: Lies drei Eingaben ein (jede in einer eigenen Zeile):- Operationstyp: entweder
divideodersqrt - Erste Zahl (double)
- Zweite Zahl (double): wird nur für die Division verwendet, für sqrt ignoriert
Erstelle ein
Calculator-Objekt und führe die angeforderte Operation innerhalb eines try-Blocks aus. Wenn die Operation erfolgreich ist, gibResult: [value]aus. Wenn eine Ausnahme abgefangen wird, gibError: [message]aus, wobei die Nachricht aus derwhat()-Methode der Ausnahme stammt.Gib nach dem try-catch-Block (unabhängig davon, ob eine Ausnahme aufgetreten ist)
Calculation completeaus, um zu zeigen, dass das Programm nach der Behandlung von Ausnahmen weiter ausgeführt wird.- Operationstyp: entweder
Beispielsweise mit den Eingaben divide, 10 und 2:
Result: 5
Calculation completeMit den Eingaben divide, 10 und 0:
Error: Division by zero
Calculation completeMit den Eingaben divide, -5 und 2:
Error: Negative values not allowed
Calculation completeMit den Eingaben sqrt, 16 und 0:
Result: 4
Calculation completeMit den Eingaben sqrt, -9 und 0:
Error: Cannot compute square root of negative number
Calculation completeBeachte, wie das Programm Fehler auf geeignete Weise behandelt, indem es Ausnahmen abfängt und aussagekräftige Nachrichten anzeigt, und anschließend die Ausführung fortsetzt. Dieses Muster hält deine Fehlerbehandlung übersichtlich und deine Hauptlogik auf den normalen Ablauf konzentriert.
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: Verwende einen try-catch-Block, um die Operation durchzuführen
// - Wenn operation "divide" ist, rufe calc.divide(num1, num2) auf
// - Wenn operation "sqrt" ist, rufe calc.safeSqrt(num1) auf
// - Bei Erfolg gib "Result: " gefolgt vom Ergebnis aus
// - Fange Ausnahmen ab und gib "Error: " gefolgt von der Ausnahme-Nachricht aus (verwende what())
// - After the try-catch, always print "Calculation complete"
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 & QuelldateienNamespaces & GültigkeitsbereichEinführung in OOP mit C++Klassen vs. ObjekteDer „this“-ZeigerMethoden (Member-Funktionen)Attribute (Datenmember)Grundlagen zu Ctors & DtorsRückblick – Einfacher Taschenrechner4Klassenattribute
Instanz- vs. statische MemberGetter und SetterConst-MemberfunktionenMutable-SchlüsselwortStatische Methoden und VariablenFriend-Funktionen und -KlassenZusammenfassung – Bankkontoverwaltung7Vererbung
Grundlagen der VererbungZugriffsebenen bei der VererbungAufrufreihenfolge von Ctor & DtorÜberschreiben von MethodenVirtuelle Funktionen & VTableMehrfachvererbungVirtuelle VererbungRückblick – Mitarbeiterhierarchie2Speicherverwaltung
Stack- vs. Heap-SpeicherZeiger und ReferenzenDynamischer Speicher (new/delete)Intelligente Zeiger in C++RAII in C++Rückblick – Dynamischer Array-Manager5Kapselung
Zugriffsbezeichner in C++Zugriffsbezeichner im DetailInformationsverbergungStruct vs. KlasseVerschachtelte und innere KlassenWiederholung – Studierendenverwaltungssystem8Polymorphie
Polymorphie zur Compilezeit vs. LaufzeitFunktionsüberladungVirtuelle Funktionen erneut betrachtetRein virtuelle FunktionenAbstrakte KlassenInterface-Design in C++Dynamic Casting & RTTIRückblick – Shape-Calculator11Fortgeschrittene OOP-Konzepte
Komposition vs. VererbungMixins mittels CRTPPimpl-IdiomTyplöschungEnum-Klassen & starke TypisierungAusnahmebehandlung in OOPBenutzerdefinierte Ausnahmehierarchien3Konstruktoren & Destruktoren
StandardkonstruktorParametrisierter KonstruktorKopierkonstruktorMove-KonstruktorKonstruktor-InitialisierungslistenDelegierende KonstruktorenDestruktor im DetailDrei-/Fünf-/Null-RegelZusammenfassung – String-Klasse6Operatorüberladung
Einführung in die OperatorüberladungArithmetische OperatorüberladungVergleichsoperatorüberladungStream-OperatorenZuweisungsoperatorüberladungÜberladung der Operatoren [] und ()TypkonvertierungsoperatorenWiederholung – Matrix-Klasse9Templates
FunktionstemplatesKlassen-TemplatesTemplate-SpezialisierungVariadische TemplatesGrundlagen zu SFINAE und Type TraitsRückblick – Generischer ContainerÜbe selbstständig: Online-C++-Compiler