Gérer les exceptions en C++

C++Beginner
Pratiquer maintenant

Introduction

Dans ce laboratoire (lab), vous allez apprendre les concepts fondamentaux de la gestion des exceptions en C++. Vous commencerez par comprendre comment lancer (throw) et capturer (catch) des exceptions de base, qui sont un moyen de gérer les erreurs d'exécution ou les situations inattendues dans votre programme. Vous explorerez ensuite l'utilisation des mots-clés try, catch et throw, ainsi que des classes d'exceptions standard telles que std::exception. De plus, vous apprendrez à définir des classes d'exceptions personnalisées et à implémenter plusieurs blocs catch pour différents types d'exceptions. Enfin, vous explorerez l'utilisation de blocs try-catch imbriqués pour gérer les exceptions à différents niveaux de votre programme.

Lancer et capturer des exceptions de base

Dans cette étape, vous allez apprendre les concepts fondamentaux de la gestion des exceptions en C++, en vous concentrant sur la manière de lancer (throw) et de capturer (catch) des exceptions de base. Les exceptions sont un moyen de gérer les erreurs d'exécution ou les situations inattendues dans votre programme.

Ouvrez le WebIDE et créez un nouveau fichier appelé basic_exceptions.cpp dans le répertoire ~/project :

touch ~/project/basic_exceptions.cpp

Ajoutez le code suivant à basic_exceptions.cpp :

#include <iostream>
#include <stdexcept>

int divide(int numerator, int denominator) {
    // Throw an exception if denominator is zero
    if (denominator == 0) {
        throw std::runtime_error("Division by zero is not allowed!");
    }
    return numerator / denominator;
}

int main() {
    try {
        // Attempt a normal division
        int result1 = divide(10, 2);
        std::cout << "10 / 2 = " << result1 << std::endl;

        // Attempt division by zero
        int result2 = divide(10, 0);
        std::cout << "This line will not be executed" << std::endl;
    }
    catch (const std::exception& e) {
        // Catch and handle the exception
        std::cout << "Error: " << e.what() << std::endl;
    }

    return 0;
}

Décortiquons les éléments clés :

  1. Mot-clé throw :

    • Utilisé pour générer une exception lorsqu'une erreur se produit
    • Dans cet exemple, nous lançons une std::runtime_error lorsque l'on tente de diviser par zéro
  2. Bloc try :

    • Contient le code qui peut générer une exception
    • Vous permet d'effectuer des opérations risquées
    • Si une exception se produit, le contrôle du programme passe au bloc catch
  3. Bloc catch :

    • Gère l'exception lancée dans le bloc try
    • Capture les exceptions d'un type spécifique (ici, std::exception)
    • Utilise e.what() pour obtenir le message d'erreur

Compilez et exécutez le programme :

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

Exemple de sortie :

10 / 2 = 5
Error: Division by zero is not allowed!

Points clés concernant la gestion des exceptions de base :

  • Les exceptions offrent un moyen de gérer les erreurs d'exécution de manière élégante
  • throw génère une exception
  • try et catch fonctionnent ensemble pour gérer les situations exceptionnelles
  • Empêche les plantages du programme en fournissant une gestion contrôlée des erreurs

Comprendre les mots-clés try, catch et throw

Dans cette étape, vous allez approfondir vos connaissances sur les trois mots-clés clés de la gestion des exceptions en C++ : try, catch et throw. Ces mots-clés fonctionnent ensemble pour créer un mécanisme de gestion des erreurs robuste dans vos programmes.

Ouvrez le WebIDE et créez un nouveau fichier appelé exception_keywords.cpp dans le répertoire ~/project :

touch ~/project/exception_keywords.cpp

Ajoutez le code suivant à exception_keywords.cpp :

#include <iostream>
#include <string>

// Function that might throw an exception
int processAge(int age) {
    // Throw an exception for invalid age
    if (age < 0) {
        throw std::string("Age cannot be negative");
    }
    if (age > 120) {
        throw std::string("Age is unrealistically high");
    }
    return age;
}

int main() {
    // First try block: handling age validation
    try {
        // Successful case
        int validAge = processAge(25);
        std::cout << "Valid age: " << validAge << std::endl;

        // This will throw an exception
        int invalidAge1 = processAge(-5);
        std::cout << "This line will not be executed" << std::endl;
    }
    catch (const std::string& errorMessage) {
        // Catch block to handle the thrown exception
        std::cout << "Error: " << errorMessage << std::endl;
    }

    // Second try block: another example
    try {
        // This will throw another exception
        int invalidAge2 = processAge(150);
        std::cout << "This line will also not be executed" << std::endl;
    }
    catch (const std::string& errorMessage) {
        std::cout << "Error: " << errorMessage << std::endl;
    }

    return 0;
}

Décortiquons les éléments clés :

  1. Mot-clé throw :

    • Utilisé pour générer une exception lorsqu'une condition spécifique est remplie
    • Peut lancer différents types d'objets (chaînes de caractères, entiers, objets personnalisés)
    • Arrête immédiatement l'exécution de la fonction actuelle
  2. Bloc try :

    • Contient le code qui peut générer une exception
    • Vous permet d'effectuer des opérations risquées
    • Si une exception se produit, le contrôle du programme passe au bloc catch correspondant
  3. Bloc catch :

    • Capture et gère des types spécifiques d'exceptions
    • Peut avoir plusieurs blocs catch pour différents types d'exceptions
    • Empêche le programme de planter en gérant les erreurs de manière élégante

Compilez et exécutez le programme :

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

Exemple de sortie :

Valid age: 25
Error: Age cannot be negative
Error: Age is unrealistically high

Points clés concernant les mots-clés d'exception :

  • throw signale une condition d'erreur
  • try définit un bloc de code qui peut générer une exception
  • catch gère l'exception et empêche la terminaison du programme
  • Les exceptions offrent une manière structurée de gérer les erreurs d'exécution

Utiliser les classes d'exceptions standard (std::exception)

Dans cette étape, vous allez apprendre à propos des classes d'exceptions standard en C++ et à utiliser la hiérarchie std::exception pour gérer différents types d'erreurs d'exécution. La bibliothèque standard C++ fournit un ensemble de classes d'exceptions prédéfinies qui couvrent diverses situations d'erreur.

Ouvrez le WebIDE et créez un nouveau fichier appelé standard_exceptions.cpp dans le répertoire ~/project :

touch ~/project/standard_exceptions.cpp

Ajoutez le code suivant à standard_exceptions.cpp :

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

double divideNumbers(double numerator, double denominator) {
    // Check for division by zero using std::runtime_error
    if (denominator == 0) {
        throw std::runtime_error("Division by zero is not allowed!");
    }
    return numerator / denominator;
}

void checkArrayIndex(int* arr, int size, int index) {
    // Check for out-of-range access using std::out_of_range
    if (index < 0 || index >= size) {
        throw std::out_of_range("Array index is out of bounds!");
    }
    std::cout << "Value at index " << index << ": " << arr[index] << std::endl;
}

int main() {
    try {
        // Demonstrate division by zero exception
        std::cout << "Attempting division:" << std::endl;
        double result = divideNumbers(10, 0);
    }
    catch (const std::runtime_error& e) {
        std::cout << "Runtime Error: " << e.what() << std::endl;
    }

    try {
        // Demonstrate array index out of range exception
        int numbers[] = {1, 2, 3, 4, 5};
        int arraySize = 5;

        std::cout << "\nAccessing array elements:" << std::endl;
        checkArrayIndex(numbers, arraySize, 2);  // Valid index
        checkArrayIndex(numbers, arraySize, 10); // Invalid index
    }
    catch (const std::out_of_range& e) {
        std::cout << "Out of Range Error: " << e.what() << std::endl;
    }

    return 0;
}

Explorons les classes d'exceptions standard :

  1. std::exception :

    • Classe de base pour toutes les exceptions standard
    • Fournit une méthode virtuelle what() pour obtenir la description de l'erreur
  2. Classes d'exceptions dérivées courantes :

    • std::runtime_error : Pour les erreurs d'exécution qui ne peuvent être détectées qu'au cours de l'exécution du programme
    • std::out_of_range : Lorsqu'un index ou un itérateur est en dehors de la plage valide
    • D'autres classes courantes incluent std::logic_error, std::invalid_argument, etc.

Compilez et exécutez le programme :

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

Exemple de sortie :

Attempting division:
Runtime Error: Division by zero is not allowed!

Accessing array elements:
Value at index 2: 3
Out of Range Error: Array index is out of bounds!

Points clés concernant les classes d'exceptions standard :

  • Offrent une manière structurée de gérer différents types d'erreurs
  • Chaque classe d'exception a un objectif spécifique
  • La méthode what() retourne un message d'erreur descriptif
  • Aide à créer une gestion des erreurs plus robuste et informative

Définir des classes d'exceptions personnalisées

Dans cette étape, vous allez apprendre à créer vos propres classes d'exceptions personnalisées en C++. Les exceptions personnalisées vous permettent de définir des types d'erreurs spécifiques adaptés aux besoins uniques de votre application.

Ouvrez le WebIDE et créez un nouveau fichier appelé custom_exceptions.cpp dans le répertoire ~/project :

touch ~/project/custom_exceptions.cpp

Ajoutez le code suivant à custom_exceptions.cpp :

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

// Custom exception class for bank account errors
class InsufficientFundsException : public std::runtime_error {
public:
    // Constructor that takes account balance and withdrawal amount
    InsufficientFundsException(double balance, double amount)
        : std::runtime_error("Insufficient funds"),
          currentBalance(balance),
          withdrawalAmount(amount) {}

    // Method to get detailed error information
    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) {
        // Check if withdrawal amount exceeds current balance
        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 {
        // Create a bank account with initial balance
        BankAccount account(100.0);

        // Attempt a valid withdrawal
        account.withdraw(50.0);

        // Attempt an invalid withdrawal
        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;
}

Décortiquons la classe d'exception personnalisée :

  1. Hériter de std::runtime_error :

    • Fournit une base pour les classes d'exceptions personnalisées
    • Permet d'utiliser la méthode what() pour obtenir la description de l'erreur
  2. Caractéristiques des exceptions personnalisées :

    • Constructeur avec un contexte d'erreur supplémentaire
    • Méthodes pour récupérer des détails spécifiques sur l'erreur
    • Fournit une gestion des erreurs plus informative

Compilez et exécutez le programme :

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

Exemple de sortie :

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

Points clés concernant les classes d'exceptions personnalisées :

  • Hériter des classes d'exceptions standard
  • Ajouter un contexte d'erreur spécifique et des méthodes
  • Fournir des informations plus détaillées sur l'erreur
  • Améliorer la gestion des erreurs et le débogage

Implémenter plusieurs blocs catch pour différents types d'exceptions

Dans cette étape, vous allez apprendre à gérer plusieurs types d'exceptions en utilisant différents blocs catch. Cette approche vous permet de fournir une gestion d'erreur spécifique pour divers types d'exceptions qui pourraient se produire dans votre programme.

Ouvrez le WebIDE et créez un nouveau fichier appelé multiple_catch.cpp dans le répertoire ~/project :

touch ~/project/multiple_catch.cpp

Ajoutez le code suivant à multiple_catch.cpp :

#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) {
        // Validate name length
        if (studentName.length() < 2) {
            throw std::length_error("Name is too short");
        }

        // Validate age range
        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;

    // First attempt: Short name
    try {
        student.setStudent("A", 25);
    }
    catch (const std::length_error& e) {
        std::cout << "Length Error: " << e.what() << std::endl;
    }
    catch (const InvalidAgeException& e) {
        std::cout << "Age Error: " << e.what()
                  << " (" << e.getInvalidAge() << ")" << std::endl;
    }

    // Second attempt: Invalid age
    try {
        student.setStudent("John Doe", 150);
    }
    catch (const std::length_error& e) {
        std::cout << "Length Error: " << e.what() << std::endl;
    }
    catch (const InvalidAgeException& e) {
        std::cout << "Age Error: " << e.what()
                  << " (" << e.getInvalidAge() << ")" << std::endl;
    }

    // Successful attempt
    try {
        student.setStudent("Alice", 20);
        student.displayInfo();
    }
    catch (const std::length_error& e) {
        std::cout << "Length Error: " << e.what() << std::endl;
    }
    catch (const InvalidAgeException& e) {
        std::cout << "Age Error: " << e.what()
                  << " (" << e.getInvalidAge() << ")" << std::endl;
    }

    return 0;
}

Décortiquons les plusieurs blocs catch :

  1. Plusieurs blocs catch :

    • Permettent de gérer différents types d'exceptions
    • Sont exécutés dans l'ordre du plus spécifique au plus général
    • Chaque bloc peut gérer un type d'exception spécifique
  2. Stratégie de gestion des exceptions :

    • Le premier bloc catch gère std::length_error
    • Le deuxième bloc catch gère InvalidAgeException
    • Fournit des messages d'erreur spécifiques pour différents scénarios

Compilez et exécutez le programme :

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

Exemple de sortie :

Length Error: Name is too short
Age Error: Invalid age (150)
Name: Alice, Age: 20

Points clés concernant les plusieurs blocs catch :

  • Gérer différents types d'exceptions séparément
  • Fournir une gestion d'erreur spécifique pour chaque exception
  • L'ordre est important lors de la capture des exceptions
  • Permet une gestion plus précise des erreurs

Utiliser des blocs try-catch imbriqués

Dans cette étape, vous allez apprendre à utiliser des blocs try-catch imbriqués pour gérer des scénarios d'erreur complexes et fournir une gestion d'exceptions plus précise. Les blocs try-catch imbriqués vous permettent de gérer les exceptions à différents niveaux de votre code.

Ouvrez le WebIDE et créez un nouveau fichier appelé nested_exceptions.cpp dans le répertoire ~/project :

touch ~/project/nested_exceptions.cpp

Ajoutez le code suivant à nested_exceptions.cpp :

#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 {
            // Simulate file reading
            if (filename.empty()) {
                throw FileReadError("Empty filename");
            }

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

            try {
                // Simulate data processing
                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;

                // Rethrow the exception to outer 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) {
        // Simulate data validation
        if (filename == "corrupt.txt") {
            throw std::runtime_error("Corrupt file data");
        }
        std::cout << "Data validation successful" << std::endl;
    }
};

int main() {
    DataProcessor processor;

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

    // Scenario 2: Corrupt file
    std::cout << "\nScenario 2: Corrupt File" << std::endl;
    processor.processFile("corrupt.txt");

    // Scenario 3: Valid file
    std::cout << "\nScenario 3: Valid File" << std::endl;
    processor.processFile("data.txt");

    return 0;
}

Décortiquons les blocs try-catch imbriqués :

  1. Bloc try-catch externe :

    • Gère les exceptions au niveau du fichier
    • Capture FileReadError et d'autres erreurs potentielles
  2. Bloc try-catch interne :

    • Gère les exceptions spécifiques au traitement des données
    • Peut relancer des exceptions au bloc try-catch externe
  3. Gestionnaire générique (catch (...)) :

    • Capture toutes les exceptions inattendues
    • Fournit une couche finale de gestion des erreurs

Compilez et exécutez le programme :

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

Exemple de sortie :

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

Points clés concernant les blocs try-catch imbriqués :

  • Fournissent plusieurs niveaux de gestion des exceptions
  • Permettent une gestion plus détaillée des erreurs
  • Peuvent relancer des exceptions aux blocs try-catch externes
  • Utiles pour les scénarios d'erreur complexes

Résumé

Dans ce laboratoire, vous avez appris les concepts fondamentaux de la gestion des exceptions en C++. Vous avez commencé par comprendre comment lancer et capturer des exceptions de base en utilisant les mots-clés throw, try et catch. Vous avez exploré l'utilisation de la classe d'exception std::runtime_error pour gérer les erreurs de division par zéro. De plus, vous avez appris à accéder au message d'erreur à l'aide de la fonction e.what() dans le bloc catch. Ces techniques permettent de gérer les erreurs d'exécution de manière élégante et d'éviter les plantages du programme, vous permettant d'écrire des applications C++ plus robustes et fiables.