Benutzerdefinierte Ausnahmehierarchien
Teil des Abschnitts Objektorientierte Programmierung der C++-Journey von Coddy. Lektion 82 von 104.
Während die Standardbibliothek nützliche Ausnahme-Klassen wie std::runtime_error bereitstellt, profitieren Anwendungen aus der Praxis oft davon, benutzerdefinierte Ausnahmehierarchien zu erstellen. Indem du eigene Ausnahme-Klassen definierst, die von Standardausnahmen erben, kannst du domänenspezifische Fehlertypen erstellen, die zusätzlichen Kontext enthalten und eine präzisere Fehlerbehandlung ermöglichen.
Eine benutzerdefinierte Ausnahme kann typischerweise von std::exception oder einer ihrer abgeleiteten Klassen erben und die Methode what() überschreiben:
#include <exception>
#include <string>
class DatabaseException : public std::exception {
std::string message;
public:
DatabaseException(const std::string& msg) : message(msg) {}
const char* what() const noexcept override { return message.c_str(); }
};
class ConnectionException : public DatabaseException {
public:
ConnectionException(const std::string& host)
: DatabaseException("Failed to connect to: " + host) {}
};
class QueryException : public DatabaseException {
public:
QueryException(const std::string& query)
: DatabaseException("Invalid query: " + query) {}
};Diese Hierarchie ermöglicht es catch blocks, exceptions auf verschiedenen Ebenen der Spezifität zu behandeln:
try {
// Datenbankoperationen...
throw ConnectionException("localhost");
} catch (const ConnectionException& e) {
std::cout << "Connection issue: " << e.what() << "\n";
} catch (const DatabaseException& e) {
std::cout << "Database error: " << e.what() << "\n";
} catch (const std::exception& e) {
std::cout << "General error: " << e.what() << "\n";
}Der noexcept-Spezifizierer bei what() ist wichtig – er garantiert, dass die Methode keine Ausnahme auslöst, was von der Schnittstelle der Basisklasse verlangt wird. Ordne deine Catch-Blöcke vom spezifischsten zum allgemeinsten, da C++ den ersten passenden Handler verwendet.
Aufgabe
EinfachErstellen wir ein Dateiverarbeitungssystem mit einer benutzerdefinierten Ausnahmehierarchie, die verschiedene Arten dateibezogener Fehler behandelt. Du erstellst domänenspezifische Ausnahmen, die von einer grundlegenden Ausnahmeklasse erben und dadurch eine präzise Fehlerbehandlung auf verschiedenen Spezifitätsebenen ermöglichen.
Du organisierst deinen Code über drei Dateien:
FileExceptions.h: Definiere deine benutzerdefinierte Ausnahmehierarchie für Dateioperationen.Erstelle eine grundlegende
FileException-Klasse, die vonstd::exceptionerbt. Sie sollte eine Fehlermeldung speichern und die Methodewhat()mit dem Spezifizierernoexceptüberschreiben.Erstelle anschließend zwei abgeleitete Ausnahme-Klassen:
FileNotFoundException: nimmt einen Dateinamen entgegen und erstellt eine Meldung:File not found: [filename]PermissionDeniedException: nimmt einen Dateinamen entgegen und erstellt eine Meldung:Permission denied: [filename]
Jede abgeleitete Klasse sollte ihren übergeordneten Konstruktor mit der entsprechenden Meldung aufrufen.
FileProcessor.h: Definiere eineFileProcessor-Klasse, die Dateioperationen simuliert und deine benutzerdefinierten Ausnahmen auslöst.Dein
FileProcessorsollte eine MethodeopenFile(const std::string& filename)besitzen, die das Öffnen einer Datei simuliert:- Wenn der Dateiname
missingenthält, löse eineFileNotFoundExceptionaus - Wenn der Dateiname
lockedenthält, löse einePermissionDeniedExceptionaus - Andernfalls gib den String
Successfully opened: [filename]zurück
- Wenn der Dateiname
main.cpp: Lies einen einzelnen Dateinamen aus der Eingabe.Erstelle einen
FileProcessorund versuche, die Datei zu öffnen. Verwende einen Try-Catch-Block mit Handlern, die von der spezifischsten zur allgemeinsten Ausnahme geordnet sind:- Catch
FileNotFoundExceptionund gib aus:File Error: [message] - Catch
PermissionDeniedExceptionund gib aus:Access Error: [message] - Catch
FileExceptionund gib aus:General File Error: [message]
Wenn keine Ausnahme ausgelöst wird, gib die von
openFile()zurückgegebene Erfolgsmeldung aus.- Catch
Beispielsweise mit der Eingabe report.txt:
Successfully opened: report.txtMit der Eingabe missing_data.csv:
File Error: File not found: missing_data.csvMit der Eingabe locked_config.ini:
Access Error: Permission denied: locked_config.iniDiese Herausforderung zeigt, wie du mit benutzerdefinierten Ausnahmehierarchien Fehler auf der jeweils passenden Detailebene behandeln kannst: spezifische Ausnahmen abfangen, wenn du eine präzise Behandlung benötigst, oder die grundlegende Ausnahme abfangen, wenn du alle Dateifehler einheitlich behandeln möchtest.
Probier es selbst
#include <iostream>
#include <string>
#include "FileExceptions.h"
#include "FileProcessor.h"
using namespace std;
int main() {
string filename;
cin >> filename;
FileProcessor processor;
// TODO: Verwende einen try-catch-Block, um Ausnahmen zu behandeln
// Ordne die Handler vom spezifischsten zum allgemeinsten:
// 1. Fange FileNotFoundException ab - gib aus: "File Error: [message]"
// 2. Fange PermissionDeniedException ab - gib aus: "Access Error: [message]"
// 3. Fange FileException ab - gib aus: "General File Error: [message]"
// Wenn keine Ausnahme, gib die Erfolgsmeldung von openFile() aus
try {
// TODO: Rufe processor.openFile() auf und behandle das Ergebnis
}
// TODO: Füge catch-Blöcke in der Reihenfolge vom spezifischsten zum allgemeinsten hinzu
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