Menu
Coddy logo textTech

Hiérarchies d'exceptions personnalisées

Fait partie de la section Programmation Orientée Objet du Journey C++ de Coddy — leçon 82 sur 104.

Alors que la bibliothèque standard fournit des classes d'exception utiles comme std::runtime_error, les applications concrètes bénéficient souvent de la création de hiérarchies d'exceptions personnalisées. En définissant vos propres classes d'exception qui héritent des exceptions standard, vous pouvez créer des types d'erreurs spécifiques au domaine qui transportent un contexte supplémentaire et permettent une gestion des erreurs plus précise.

Une classe d'exception personnalisée hérite généralement de std::exception ou de l'une de ses classes dérivées et surcharge la méthode 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(); }
};

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) {}
};

Cette hiérarchie permet aux blocs catch de gérer les exceptions à différents niveaux de spécificité :

try {
    // opérations de base de données...
    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";
}

Le spécificateur noexcept sur what() est important - il garantit que la méthode ne lèvera pas d'exception, ce qui est requis par l'interface de la classe de base. Ordonnez vos blocs catch du plus spécifique au plus général, car le C++ utilise le premier gestionnaire correspondant.

challenge icon

Défi

Facile

Construisons un système de traitement de fichiers avec une hiérarchie d'exceptions personnalisées qui gère différents types d'erreurs liées aux fichiers. Vous allez créer des exceptions spécifiques au domaine qui héritent d'une classe d'exception de base, permettant une gestion précise des erreurs à différents niveaux de spécificité.

Vous organiserez votre code sur trois fichiers :

  • FileExceptions.h : Définissez votre hiérarchie d'exceptions personnalisées pour les opérations sur les fichiers.

    Créez une classe de base FileException qui hérite de std::exception. Elle doit stocker un message d'erreur et surcharger la méthode what() avec le spécificateur noexcept.

    Ensuite, créez deux classes d'exception dérivées :

    • FileNotFoundException — prend un nom de fichier et crée un message : File not found: [filename]
    • PermissionDeniedException — prend un nom de fichier et crée un message : Permission denied: [filename]

    Chaque classe dérivée doit appeler son constructeur parent avec le message approprié.

  • FileProcessor.h : Définissez une classe FileProcessor qui simule des opérations sur les fichiers et lève vos exceptions personnalisées.

    Votre FileProcessor doit avoir une méthode openFile(const std::string& filename) qui simule l'ouverture d'un fichier :

    • Si le nom du fichier contient missing, levez une FileNotFoundException
    • Si le nom du fichier contient locked, levez une PermissionDeniedException
    • Sinon, retournez la chaîne Successfully opened: [filename]
  • main.cpp : Lisez un seul nom de fichier à partir de l'entrée.

    Créez un FileProcessor et tentez d'ouvrir le fichier. Utilisez un bloc try-catch avec des gestionnaires ordonnés du plus spécifique au plus général :

    1. Interceptez FileNotFoundException et affichez : File Error: [message]
    2. Interceptez PermissionDeniedException et affichez : Access Error: [message]
    3. Interceptez FileException et affichez : General File Error: [message]

    Si aucune exception n'est levée, affichez le message de succès retourné par openFile().

Par exemple, avec l'entrée report.txt :

Successfully opened: report.txt

Avec l'entrée missing_data.csv :

File Error: File not found: missing_data.csv

Avec l'entrée locked_config.ini :

Access Error: Permission denied: locked_config.ini

Ce défi démontre comment les hiérarchies d'exceptions personnalisées vous permettent de gérer les erreurs au niveau de détail approprié — en interceptant des exceptions spécifiques lorsque vous avez besoin d'une gestion précise, ou en interceptant l'exception de base lorsque vous souhaitez gérer toutes les erreurs de fichier de manière uniforme.

Aide-mémoire

Les classes d'exception personnalisées héritent de std::exception ou de ses classes dérivées et surchargent la méthode 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(); }
};

Créez des hiérarchies d'exceptions en héritant d'exceptions de base personnalisées :

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

Gérez les exceptions à différents niveaux de spécificité en ordonnant les blocs catch du plus spécifique au plus général :

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";
}

Le spécificateur noexcept sur what() garantit que la méthode ne lèvera pas d'exception, ce qui est requis par l'interface de la classe de base.

Essayez vous-même

#include <iostream>
#include <string>
#include "FileExceptions.h"
#include "FileProcessor.h"

using namespace std;

int main() {
    string filename;
    cin >> filename;
    
    FileProcessor processor;
    
    // TODO: Utiliser un bloc try-catch pour gérer les exceptions
    // Ordonner les gestionnaires du plus spécifique au plus général :
    // 1. Intercepter FileNotFoundException - afficher : "File Error: [message]"
    // 2. Intercepter PermissionDeniedException - afficher : "Access Error: [message]"
    // 3. Intercepter FileException - afficher : "General File Error: [message]"
    // Si aucune exception n'est levée, afficher le message de succès de openFile()
    
    try {
        // TODO: Appeler processor.openFile() et gérer le résultat
        
    }
    // TODO: Ajouter des blocs catch dans l'ordre du plus spécifique au plus général
    
    return 0;
}
quiz iconTestez-vous

Cette leçon comprend un petit quiz. Commencez la leçon pour y répondre et suivre votre progression.

Toutes les leçons de Programmation Orientée Objet