Menu
Coddy logo textTech

Eigene Exception-Hierarchien

Teil des Abschnitts Objektorientierte Programmierung der C++-Journey von Coddy — Lektion 82 von 104.

Während die Standardbibliothek nützliche Ausnahmeklassen wie std::runtime_error bietet, profitieren Anwendungen in der Praxis oft von der Erstellung benutzerdefinierter Ausnahmehierarchien. Indem Sie Ihre eigenen Ausnahmeklassen definieren, die von Standardausnahmen erben, können Sie domänenspezifische Fehlertypen erstellen, die zusätzlichen Kontext enthalten und eine präzisere Fehlerbehandlung ermöglichen.

Eine benutzerdefinierte Exception-Klasse erbt normalerweise von std::exception oder einer ihrer abgeleiteten Klassen und überschreibt die what()-Methode:

#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-Blöcken, Ausnahmen auf verschiedenen Spezifitätsebenen 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 für what() ist wichtig – er garantiert, dass die Methode keine Exceptions wirft, was von der Schnittstelle der Basisklasse gefordert wird. Ordnen Sie Ihre catch-Blöcke vom Spezifischsten zum Allgemeinsten an, da C++ den ersten passenden Handler verwendet.

challenge icon

Aufgabe

Einfach

Lassen Sie uns ein Dateiverarbeitungssystem mit einer benutzerdefinierten Ausnahmehierarchie erstellen, die verschiedene Arten von dateibezogenen Fehlern verarbeitet. Sie werden domänenspezifische Ausnahmen erstellen, die von einer Basis-Ausnahmeklasse erben, was eine präzise Fehlerbehandlung auf verschiedenen Ebenen der Spezifität ermöglicht.

Sie werden Ihren Code über drei Dateien organisieren:

  • FileExceptions.h: Definieren Sie Ihre benutzerdefinierte Ausnahmehierarchie für Dateioperationen.

    Erstellen Sie eine Basisklasse FileException, die von std::exception erbt. Sie sollte eine Fehlermeldung speichern und die Methode what() mit dem Spezifizierer noexcept überschreiben.

    Erstellen Sie dann zwei abgeleitete Ausnahmeklassen:

    • 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 Eltern-Konstruktor mit der entsprechenden Meldung aufrufen.

  • FileProcessor.h: Definieren Sie eine Klasse FileProcessor, die Dateioperationen simuliert und Ihre benutzerdefinierten Ausnahmen auslöst.

    Ihr FileProcessor sollte eine Methode openFile(const std::string& filename) haben, die das Öffnen einer Datei simuliert:

    • Wenn der Dateiname missing enthält, lösen Sie eine FileNotFoundException aus
    • Wenn der Dateiname locked enthält, lösen Sie eine PermissionDeniedException aus
    • Andernfalls geben Sie den String Successfully opened: [filename] zurück
  • main.cpp: Lesen Sie einen einzelnen Dateinamen von der Eingabe ein.

    Erstellen Sie einen FileProcessor und versuchen Sie, die Datei zu öffnen. Verwenden Sie einen try-catch-Block mit Handlern, die von der spezifischsten zur allgemeinsten geordnet sind:

    1. Fangen Sie FileNotFoundException ab und geben Sie aus: File Error: [message]
    2. Fangen Sie PermissionDeniedException ab und geben Sie aus: Access Error: [message]
    3. Fangen Sie FileException ab und geben Sie aus: General File Error: [message]

    Wenn keine Ausnahme ausgelöst wird, geben Sie die von openFile() zurückgegebene Erfolgsmeldung aus.

Zum Beispiel bei der Eingabe report.txt:

Successfully opened: report.txt

Bei der Eingabe missing_data.csv:

File Error: File not found: missing_data.csv

Bei der Eingabe locked_config.ini:

Access Error: Permission denied: locked_config.ini

Diese Herausforderung zeigt, wie benutzerdefinierte Ausnahmehierarchien es Ihnen ermöglichen, Fehler auf der angemessenen Detailebene zu behandeln – indem Sie spezifische Ausnahmen abfangen, wenn Sie eine präzise Handhabung benötigen, oder die Basisausnahme abfangen, wenn Sie alle Dateifehler einheitlich behandeln möchten.

Spickzettel

Benutzerdefinierte Exception-Klassen erben von std::exception oder deren abgeleiteten Klassen und überschreiben die Methode what():

#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(); }
};

Erstellen Sie Exception-Hierarchien, indem Sie von benutzerdefinierten Basis-Exceptions erben:

class ConnectionException : public DatabaseException {
public:
    ConnectionException(const std::string& host) 
        : DatabaseException("Failed to connect to: " + host) {}
};

Behandeln Sie Exceptions auf verschiedenen Spezifitätsebenen, indem Sie Catch-Blöcke vom Spezifischsten zum Allgemeinsten ordnen:

try {
    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 Bezeichner noexcept bei what() garantiert, dass die Methode keine Exception wirft, was von der Schnittstelle der Basisklasse gefordert wird.

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 Exceptions zu behandeln
    // Ordne die Handler vom Spezifischsten zum Allgemeinsten:
    // 1. Fange FileNotFoundException ab - Ausgabe: "File Error: [message]"
    // 2. Fange PermissionDeniedException ab - Ausgabe: "Access Error: [message]"
    // 3. Fange FileException ab - Ausgabe: "General File Error: [message]"
    // Wenn keine Exception auftritt, gib die Erfolgsmeldung von openFile() aus
    
    try {
        // TODO: Rufe processor.openFile() auf und verarbeite das Ergebnis
        
    }
    // TODO: Füge catch-Blöcke in der Reihenfolge vom Spezifischsten zum Allgemeinsten hinzu
    
    return 0;
}
quiz iconTeste dich selbst

Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.

Alle Lektionen in Objektorientierte Programmierung