Ausnahmen in C++ behandeln

C++Beginner
Jetzt üben

Einführung

In diesem Lab werden Sie die grundlegenden Konzepte der Ausnahmebehandlung (Exception Handling) in C++ lernen. Sie beginnen damit, zu verstehen, wie Sie grundlegende Ausnahmen werfen (throw) und fangen (catch) können. Dies ist eine Möglichkeit, Laufzeitfehler oder unerwartete Situationen in Ihrem Programm zu behandeln. Anschließend werden Sie die Verwendung der Schlüsselwörter try, catch und throw sowie von Standard-Ausnahmeklassen wie std::exception untersuchen. Darüber hinaus werden Sie lernen, wie Sie benutzerdefinierte Ausnahmeklassen definieren und mehrere catch-Blöcke für verschiedene Ausnahmetypen implementieren können. Abschließend werden Sie die Verwendung von geschachtelten try-catch-Blöcken untersuchen, um Ausnahmen auf verschiedenen Ebenen Ihres Programms zu behandeln.

Grundlegende Ausnahmen werfen und fangen

In diesem Schritt werden Sie die grundlegenden Konzepte der Ausnahmebehandlung (Exception Handling) in C++ lernen, wobei der Schwerpunkt auf dem Werfen und Fangen von grundlegenden Ausnahmen liegt. Ausnahmen sind eine Möglichkeit, Laufzeitfehler oder unerwartete Situationen in Ihrem Programm zu behandeln.

Öffnen Sie die WebIDE und erstellen Sie eine neue Datei namens basic_exceptions.cpp im Verzeichnis ~/project:

touch ~/project/basic_exceptions.cpp

Fügen Sie den folgenden Code in die Datei basic_exceptions.cpp ein:

#include <iostream>
#include <stdexcept>

int divide(int numerator, int denominator) {
    // Werfen Sie eine Ausnahme, wenn der Nenner Null ist
    if (denominator == 0) {
        throw std::runtime_error("Division durch Null ist nicht erlaubt!");
    }
    return numerator / denominator;
}

int main() {
    try {
        // Versuchen Sie eine normale Division
        int result1 = divide(10, 2);
        std::cout << "10 / 2 = " << result1 << std::endl;

        // Versuchen Sie die Division durch Null
        int result2 = divide(10, 0);
        std::cout << "Diese Zeile wird nicht ausgeführt" << std::endl;
    }
    catch (const std::exception& e) {
        // Fangen und behandeln Sie die Ausnahme
        std::cout << "Fehler: " << e.what() << std::endl;
    }

    return 0;
}

Lassen Sie uns die wichtigsten Bestandteile analysieren:

  1. Schlüsselwort throw:

    • Wird verwendet, um eine Ausnahme zu generieren, wenn ein Fehler auftritt
    • In diesem Beispiel werfen wir eine std::runtime_error, wenn eine Division durch Null versucht wird
  2. try-Block:

    • Enthält Code, der eine Ausnahme generieren kann
    • Ermöglicht es Ihnen, riskante Operationen auszuführen
    • Wenn eine Ausnahme auftritt, wird die Programmsteuerung an den catch-Block übergeben
  3. catch-Block:

    • Behandelt die im try-Block geworfene Ausnahme
    • Fängt Ausnahmen eines bestimmten Typs (hier std::exception)
    • Nutzt e.what(), um die Fehlermeldung zu erhalten

Kompilieren und führen Sie das Programm aus:

g++ basic_exceptions.cpp -o basic_exceptions
./basic_exceptions

Beispielausgabe:

10 / 2 = 5
Fehler: Division durch Null ist nicht erlaubt!

Wichtige Punkte zur grundlegenden Ausnahmebehandlung:

  • Ausnahmen bieten eine Möglichkeit, Laufzeitfehler elegant zu behandeln
  • throw generiert eine Ausnahme
  • try und catch arbeiten zusammen, um außergewöhnliche Situationen zu verwalten
  • Verhindert Programmabstürze durch kontrollierte Fehlerbehandlung

Verstehen Sie die Schlüsselwörter try, catch und throw

In diesem Schritt werden Sie tiefer in die drei wichtigsten Schlüsselwörter der Ausnahmebehandlung (Exception Handling) in C++ eintauchen: try, catch und throw. Diese Schlüsselwörter arbeiten zusammen, um einen robusten Fehlerbehandlungsmechanismus in Ihren Programmen zu schaffen.

Öffnen Sie die WebIDE und erstellen Sie eine neue Datei namens exception_keywords.cpp im Verzeichnis ~/project:

touch ~/project/exception_keywords.cpp

Fügen Sie den folgenden Code in die Datei exception_keywords.cpp ein:

#include <iostream>
#include <string>

// Funktion, die möglicherweise eine Ausnahme wirft
int processAge(int age) {
    // Werfen Sie eine Ausnahme für ein ungültiges Alter
    if (age < 0) {
        throw std::string("Das Alter kann nicht negativ sein");
    }
    if (age > 120) {
        throw std::string("Das Alter ist unrealistisch hoch");
    }
    return age;
}

int main() {
    // Erster try-Block: Überprüfung des Alters
    try {
        // Erfolgsfall
        int validAge = processAge(25);
        std::cout << "Gültiges Alter: " << validAge << std::endl;

        // Dies wird eine Ausnahme werfen
        int invalidAge1 = processAge(-5);
        std::cout << "Diese Zeile wird nicht ausgeführt" << std::endl;
    }
    catch (const std::string& errorMessage) {
        // Catch-Block, um die geworfene Ausnahme zu behandeln
        std::cout << "Fehler: " << errorMessage << std::endl;
    }

    // Zweiter try-Block: Ein weiteres Beispiel
    try {
        // Dies wird eine andere Ausnahme werfen
        int invalidAge2 = processAge(150);
        std::cout << "Diese Zeile wird ebenfalls nicht ausgeführt" << std::endl;
    }
    catch (const std::string& errorMessage) {
        std::cout << "Fehler: " << errorMessage << std::endl;
    }

    return 0;
}

Lassen Sie uns die wichtigsten Bestandteile analysieren:

  1. Schlüsselwort throw:

    • Wird verwendet, um eine Ausnahme zu generieren, wenn eine bestimmte Bedingung erfüllt ist
    • Kann verschiedene Objekttypen (Strings, Ganzzahlen, benutzerdefinierte Objekte) werfen
    • Beendet sofort die Ausführung der aktuellen Funktion
  2. try-Block:

    • Enthält Code, der möglicherweise eine Ausnahme generiert
    • Ermöglicht es Ihnen, riskante Operationen auszuführen
    • Wenn eine Ausnahme auftritt, wird die Programmsteuerung an den passenden catch-Block übergeben
  3. catch-Block:

    • Fängt und behandelt bestimmte Typen von Ausnahmen
    • Kann mehrere Catch-Blöcke für verschiedene Ausnahmetypen haben
    • Verhindert, dass das Programm abstürzt, indem es Fehler elegant behandelt

Kompilieren und führen Sie das Programm aus:

g++ exception_keywords.cpp -o exception_keywords
./exception_keywords

Beispielausgabe:

Gültiges Alter: 25
Fehler: Das Alter kann nicht negativ sein
Fehler: Das Alter ist unrealistisch hoch

Wichtige Punkte zu den Ausnahme-Schlüsselwörtern:

  • throw signalisiert einen Fehlerzustand
  • try definiert einen Codeblock, der möglicherweise eine Ausnahme generiert
  • catch behandelt die Ausnahme und verhindert das Beenden des Programms
  • Ausnahmen bieten eine strukturierte Möglichkeit, Laufzeitfehler zu verwalten

Verwenden Sie Standard-Ausnahmeklassen (std::exception)

In diesem Schritt werden Sie über die Standard-Ausnahmeklassen in C++ lernen und wie Sie die std::exception-Hierarchie nutzen können, um verschiedene Arten von Laufzeitfehlern zu behandeln. Die C++-Standardbibliothek bietet eine Reihe von vordefinierten Ausnahmeklassen, die verschiedene Fehlerszenarien abdecken.

Öffnen Sie die WebIDE und erstellen Sie eine neue Datei namens standard_exceptions.cpp im Verzeichnis ~/project:

touch ~/project/standard_exceptions.cpp

Fügen Sie den folgenden Code in die Datei standard_exceptions.cpp ein:

#include <iostream>
#include <stdexcept>
#include <limits>

double divideNumbers(double numerator, double denominator) {
    // Prüfen Sie auf Division durch Null mit std::runtime_error
    if (denominator == 0) {
        throw std::runtime_error("Division durch Null ist nicht erlaubt!");
    }
    return numerator / denominator;
}

void checkArrayIndex(int* arr, int size, int index) {
    // Prüfen Sie auf Zugriff außerhalb des gültigen Bereichs mit std::out_of_range
    if (index < 0 || index >= size) {
        throw std::out_of_range("Array-Index ist außerhalb der Grenzen!");
    }
    std::cout << "Wert am Index " << index << ": " << arr[index] << std::endl;
}

int main() {
    try {
        // Demonstrieren Sie die Division-durch-Null-Ausnahme
        std::cout << "Versuch einer Division:" << std::endl;
        double result = divideNumbers(10, 0);
    }
    catch (const std::runtime_error& e) {
        std::cout << "Laufzeitfehler: " << e.what() << std::endl;
    }

    try {
        // Demonstrieren Sie die Array-Index-ausserhalb-des-Bereichs-Ausnahme
        int numbers[] = {1, 2, 3, 4, 5};
        int arraySize = 5;

        std::cout << "\nZugriff auf Array-Elemente:" << std::endl;
        checkArrayIndex(numbers, arraySize, 2);  // Gültiger Index
        checkArrayIndex(numbers, arraySize, 10); // Ungültiger Index
    }
    catch (const std::out_of_range& e) {
        std::cout << "Index-ausserhalb-des-Bereichs-Fehler: " << e.what() << std::endl;
    }

    return 0;
}

Lassen Sie uns die Standard-Ausnahmeklassen untersuchen:

  1. std::exception:

    • Basisklasse für alle Standard-Ausnahmen
    • Bietet eine virtuelle what()-Methode, um die Fehlbeschreibung zu erhalten
  2. Häufige abgeleitete Ausnahmeklassen:

    • std::runtime_error: Für Laufzeitfehler, die erst während der Programmausführung erkannt werden können
    • std::out_of_range: Wenn ein Index oder Iterator außerhalb des gültigen Bereichs liegt
    • Andere häufige Klassen sind std::logic_error, std::invalid_argument usw.

Kompilieren und führen Sie das Programm aus:

g++ standard_exceptions.cpp -o standard_exceptions
./standard_exceptions

Beispielausgabe:

Versuch einer Division:
Laufzeitfehler: Division durch Null ist nicht erlaubt!

Zugriff auf Array-Elemente:
Wert am Index 2: 3
Index-ausserhalb-des-Bereichs-Fehler: Array-Index ist außerhalb der Grenzen!

Wichtige Punkte zu den Standard-Ausnahmeklassen:

  • Bieten eine strukturierte Möglichkeit, verschiedene Arten von Fehlern zu behandeln
  • Jede Ausnahmeklasse hat einen bestimmten Zweck
  • Die what()-Methode gibt eine beschreibende Fehlermeldung zurück
  • Hilft bei der Erstellung einer robusteren und informativeren Fehlerbehandlung

Benutzerdefinierte Ausnahmeklassen definieren

In diesem Schritt werden Sie lernen, wie Sie Ihre eigenen benutzerdefinierten Ausnahmeklassen in C++ erstellen können. Benutzerdefinierte Ausnahmen ermöglichen es Ihnen, spezifische Fehlertypen zu definieren, die auf die einzigartigen Anforderungen Ihrer Anwendung zugeschnitten sind.

Öffnen Sie die WebIDE und erstellen Sie eine neue Datei namens custom_exceptions.cpp im Verzeichnis ~/project:

touch ~/project/custom_exceptions.cpp

Fügen Sie den folgenden Code in die Datei custom_exceptions.cpp ein:

#include <iostream>
#include <string>
#include <stdexcept>

// Benutzerdefinierte Ausnahmeklasse für Bankkontofehler
class InsufficientFundsException : public std::runtime_error {
public:
    // Konstruktor, der den Kontostand und den Abhebungsbetrag entgegennimmt
    InsufficientFundsException(double balance, double amount)
        : std::runtime_error("Insufficient funds"),
          currentBalance(balance),
          withdrawalAmount(amount) {}

    // Methode, um detaillierte Fehlerinformationen zu erhalten
    double getCurrentBalance() const { return currentBalance; }
    double getWithdrawalAmount() const { return withdrawalAmount; }

private:
    double currentBalance;
    double withdrawalAmount;
};

class BankAccount {
private:
    double balance;

public:
    BankAccount(double initialBalance) : balance(initialBalance) {}

    void withdraw(double amount) {
        // Prüfen, ob der Abhebungsbetrag den aktuellen Kontostand überschreitet
        if (amount > balance) {
            throw InsufficientFundsException(balance, amount);
        }
        balance -= amount;
        std::cout << "Withdrawal successful. Remaining balance: $"
                  << balance << std::endl;
    }

    double getBalance() const { return balance; }
};

int main() {
    try {
        // Erstellen Sie ein Bankkonto mit einem Anfangssaldo
        BankAccount account(100.0);

        // Versuchen Sie eine gültige Abhebung
        account.withdraw(50.0);

        // Versuchen Sie eine ungültige Abhebung
        account.withdraw(75.0);
    }
    catch (const InsufficientFundsException& e) {
        std::cout << "Error: " << e.what() << std::endl;
        std::cout << "Current Balance: $" << e.getCurrentBalance() << std::endl;
        std::cout << "Attempted Withdrawal: $" << e.getWithdrawalAmount() << std::endl;
    }

    return 0;
}

Lassen Sie uns die benutzerdefinierte Ausnahmeklasse analysieren:

  1. Ableitung von std::runtime_error:

    • Bietet eine Basis für benutzerdefinierte Ausnahmeklassen
    • Ermöglicht die Verwendung der what()-Methode, um die Fehlbeschreibung zu erhalten
  2. Merkmale der benutzerdefinierten Ausnahme:

    • Konstruktor mit zusätzlichem Fehlerkontext
    • Methoden zur Abfrage spezifischer Fehlerdetails
    • Bietet eine informativere Fehlerbehandlung

Kompilieren und führen Sie das Programm aus:

g++ custom_exceptions.cpp -o custom_exceptions
./custom_exceptions

Beispielausgabe:

Withdrawal successful. Remaining balance: $50
Error: Insufficient funds
Current Balance: $50
Attempted Withdrawal: $75

Wichtige Punkte zu benutzerdefinierten Ausnahmeklassen:

  • Ableitung von Standard-Ausnahmeklassen
  • Hinzufügen von spezifischem Fehlerkontext und Methoden
  • Bereitstellung detaillierterer Fehlerinformationen
  • Verbesserung der Fehlerbehandlung und des Debuggings

Mehrere catch-Blöcke für verschiedene Ausnahmetypen implementieren

In diesem Schritt werden Sie lernen, wie Sie verschiedene Ausnahmetypen mithilfe von mehreren Catch-Blöcken behandeln können. Dieser Ansatz ermöglicht es Ihnen, spezifische Fehlerbehandlungen für verschiedene Arten von Ausnahmen bereitzustellen, die in Ihrem Programm auftreten können.

Öffnen Sie die WebIDE und erstellen Sie eine neue Datei namens multiple_catch.cpp im Verzeichnis ~/project:

touch ~/project/multiple_catch.cpp

Fügen Sie den folgenden Code in die Datei multiple_catch.cpp ein:

#include <iostream>
#include <stdexcept>
#include <string>

class InvalidAgeException : public std::runtime_error {
public:
    InvalidAgeException(int age)
        : std::runtime_error("Invalid age"), invalidAge(age) {}

    int getInvalidAge() const { return invalidAge; }

private:
    int invalidAge;
};

class Student {
private:
    std::string name;
    int age;

public:
    void setStudent(const std::string& studentName, int studentAge) {
        // Validieren Sie die Länge des Namens
        if (studentName.length() < 2) {
            throw std::length_error("Name is too short");
        }

        // Validieren Sie den Altersbereich
        if (studentAge < 0 || studentAge > 120) {
            throw InvalidAgeException(studentAge);
        }

        name = studentName;
        age = studentAge;
    }

    void displayInfo() const {
        std::cout << "Name: " << name << ", Age: " << age << std::endl;
    }
};

int main() {
    Student student;

    // Erster Versuch: Kurzer Name
    try {
        student.setStudent("A", 25);
    }
    catch (const std::length_error& e) {
        std::cout << "Längenfehler: " << e.what() << std::endl;
    }
    catch (const InvalidAgeException& e) {
        std::cout << "Altersfehler: " << e.what()
                  << " (" << e.getInvalidAge() << ")" << std::endl;
    }

    // Zweiter Versuch: Ungültiges Alter
    try {
        student.setStudent("John Doe", 150);
    }
    catch (const std::length_error& e) {
        std::cout << "Längenfehler: " << e.what() << std::endl;
    }
    catch (const InvalidAgeException& e) {
        std::cout << "Altersfehler: " << e.what()
                  << " (" << e.getInvalidAge() << ")" << std::endl;
    }

    // Erfolgreicher Versuch
    try {
        student.setStudent("Alice", 20);
        student.displayInfo();
    }
    catch (const std::length_error& e) {
        std::cout << "Längenfehler: " << e.what() << std::endl;
    }
    catch (const InvalidAgeException& e) {
        std::cout << "Altersfehler: " << e.what()
                  << " (" << e.getInvalidAge() << ")" << std::endl;
    }

    return 0;
}

Lassen Sie uns die mehreren Catch-Blöcke analysieren:

  1. Mehrere catch-Blöcke:

    • Ermöglichen die Behandlung verschiedener Ausnahmetypen
    • Werden in der Reihenfolge von spezifisch bis allgemein ausgeführt
    • Jeder Block kann einen bestimmten Ausnahmetyp behandeln
  2. Strategie zur Ausnahmebehandlung:

    • Der erste Catch-Block behandelt std::length_error
    • Der zweite Catch-Block behandelt InvalidAgeException
    • Bietet spezifische Fehlermeldungen für verschiedene Szenarien

Kompilieren und führen Sie das Programm aus:

g++ multiple_catch.cpp -o multiple_catch
./multiple_catch

Beispielausgabe:

Längenfehler: Name is too short
Altersfehler: Invalid age (150)
Name: Alice, Age: 20

Wichtige Punkte zu mehreren Catch-Blöcken:

  • Behandeln verschiedene Ausnahmetypen separat
  • Bieten spezifische Fehlerbehandlungen für jede Ausnahme
  • Die Reihenfolge ist beim Fangen von Ausnahmen wichtig
  • Ermöglichen eine feinere Fehlerverwaltung

Geschachtelte try-catch-Blöcke verwenden

In diesem Schritt werden Sie lernen, wie Sie geschachtelte try-catch-Blöcke verwenden können, um komplexe Fehlerszenarien zu behandeln und eine feinere Ausnahmebehandlung zu ermöglichen. Geschachtelte try-catch-Blöcke ermöglichen es Ihnen, Ausnahmen auf verschiedenen Ebenen Ihres Codes zu behandeln.

Öffnen Sie die WebIDE und erstellen Sie eine neue Datei namens nested_exceptions.cpp im Verzeichnis ~/project:

touch ~/project/nested_exceptions.cpp

Fügen Sie den folgenden Code in die Datei nested_exceptions.cpp ein:

#include <iostream>
#include <stdexcept>
#include <string>

class FileReadError : public std::runtime_error {
public:
    FileReadError(const std::string& filename)
        : std::runtime_error("Error reading file"), fileName(filename) {}

    std::string getFileName() const { return fileName; }

private:
    std::string fileName;
};

class DataProcessor {
public:
    void processFile(const std::string& filename) {
        try {
            // Simulieren Sie das Lesen einer Datei
            if (filename.empty()) {
                throw FileReadError("Empty filename");
            }

            std::cout << "Reading file: " << filename << std::endl;

            try {
                // Simulieren Sie die Datenverarbeitung
                validateData(filename);
            }
            catch (const std::runtime_error& e) {
                std::cout << "Inner try-catch: Data validation error" << std::endl;
                std::cout << "Error details: " << e.what() << std::endl;

                // Werfen Sie die Ausnahme erneut an den äußeren Catch-Block
                throw;
            }
        }
        catch (const FileReadError& e) {
            std::cout << "Outer try-catch: File read error" << std::endl;
            std::cout << "Filename: " << e.getFileName() << std::endl;
        }
        catch (...) {
            std::cout << "Caught unknown exception" << std::endl;
        }
    }

private:
    void validateData(const std::string& filename) {
        // Simulieren Sie die Datenvalidierung
        if (filename == "corrupt.txt") {
            throw std::runtime_error("Corrupt file data");
        }
        std::cout << "Data validation successful" << std::endl;
    }
};

int main() {
    DataProcessor processor;

    // Szenario 1: Leerer Dateiname
    std::cout << "Scenario 1: Empty Filename" << std::endl;
    processor.processFile("");

    // Szenario 2: Beschädigte Datei
    std::cout << "\nScenario 2: Corrupt File" << std::endl;
    processor.processFile("corrupt.txt");

    // Szenario 3: Gültige Datei
    std::cout << "\nScenario 3: Valid File" << std::endl;
    processor.processFile("data.txt");

    return 0;
}

Lassen Sie uns die geschachtelten try-catch-Blöcke analysieren:

  1. Äußerer try-catch-Block:

    • Behandelt Datei-Ebene-Ausnahmen
    • Fängt FileReadError und andere potenzielle Fehler
  2. Innerer try-catch-Block:

    • Behandelt datenverarbeitungsspezifische Ausnahmen
    • Kann Ausnahmen an den äußeren Catch-Block erneut werfen
  3. Catch-all-Handler (catch (...)):

    • Fängt alle unerwarteten Ausnahmen
    • Bietet eine letzte Ebene der Fehlerbehandlung

Kompilieren und führen Sie das Programm aus:

g++ nested_exceptions.cpp -o nested_exceptions
./nested_exceptions

Beispielausgabe:

Scenario 1: Empty Filename
Outer try-catch: File read error
Filename: Empty filename

Scenario 2: Corrupt File
Reading file: corrupt.txt
Inner try-catch: Data validation error
Error details: Corrupt file data

Scenario 3: Valid File
Reading file: data.txt
Data validation successful

Wichtige Punkte zu geschachtelten try-catch-Blöcken:

  • Bieten mehrere Ebenen der Ausnahmebehandlung
  • Ermöglichen eine detailliertere Fehlerverwaltung
  • Können Ausnahmen an äußere Catch-Blöcke erneut werfen
  • Nützlich für komplexe Fehlerszenarien

Zusammenfassung

In diesem Lab haben Sie die grundlegenden Konzepte der Ausnahmebehandlung in C++ gelernt. Sie haben begonnen, zu verstehen, wie Sie grundlegende Ausnahmen mit den Schlüsselwörtern throw, try und catch werfen und fangen können. Sie haben die Verwendung der std::runtime_error-Ausnahmeklasse zur Behandlung von Divisionen durch Null erforscht. Darüber hinaus haben Sie gelernt, wie Sie die Fehlermeldung mithilfe der e.what()-Funktion innerhalb des catch-Blocks abrufen können. Diese Techniken bieten eine Möglichkeit, Laufzeitfehler elegant zu behandeln und Programmabstürze zu vermeiden, sodass Sie robusteres und zuverlässigeres C++-Software schreiben können.