Module 05 : Exceptions
Concepts Clés :
- Mots-clés try, catch, throw
- Classes d’exceptions
- Hiérarchie std::exception
- Exceptions imbriquées
- Sécurité des exceptions
Pourquoi C’est Important
Section intitulée « Pourquoi C’est Important »Les exceptions fournissent un moyen structuré de gérer les erreurs. Au lieu de vérifier les codes de retour partout, vous pouvez séparer la logique normale de la gestion des erreurs.
Comprendre les Opérateurs d’Exception
Section intitulée « Comprendre les Opérateurs d’Exception »Ce module introduit la syntaxe de gestion des exceptions. Voici ce que fait chaque mot-clé :
L’Instruction throw
Section intitulée « L’Instruction throw »throw std::runtime_error("Quelque chose s'est mal passé !");throwarrête immédiatement l’exécution normale et “lance” un objet exception- L’exception “remonte” dans la pile d’appels jusqu’à ce que quelque chose l’attrape
- Si rien ne l’attrape, le programme se termine avec une erreur
- Vous pouvez lancer n’importe quel type, mais la bonne pratique est de lancer des classes d’exception
Le Bloc try
Section intitulée « Le Bloc try »try { // Code qui pourrait lancer fonctionRisquee();} catch (...) { }trydéfinit une “zone protégée” où les exceptions peuvent être attrapées- Toute exception lancée dans un bloc
trysera attrapée par uncatchcorrespondant - Sans
try, une exception terminerait le programme
Le Bloc catch
Section intitulée « Le Bloc catch »catch (std::runtime_error& e) { // Gérer runtime_error}catch (std::exception& e) { // Gérer toute exception standard}catch (...) { // Attraper N'IMPORTE quelle exception (type inconnu)}catchspécifie comment gérer des types d’exceptions spécifiques&signifie “attraper par référence” (évite la copie, recommandé)...(trois points) signifie “tout attraper” - utiliser en dernier recours- L’ordre compte : mettez les exceptions spécifiques en premier, les générales en dernier
La Spécification d’Exception throw() (C++98)
Section intitulée « La Spécification d’Exception throw() (C++98) »const char* what() const throw(); // Promet de ne pas lancervoid fonctionRisquee() throw(std::runtime_error); // Ne peut lancer que les types listésthrow()après une déclaration de fonction est une spécification d’exception (C++98 seulement)throw()vide signifie “je promets que cette fonction ne lancera jamais”throw(Type)signifie “je ne peux lancer que ces types spécifiques”- La méthode
what()utilisethrow()car les gestionnaires d’exception doivent pouvoir l’appeler en sécurité - Note : Le C++ moderne (11+) utilise
noexceptà la place, mais le cursus 42 utilise C++98
Syntaxe de Classe Imbriquée avec ::
Section intitulée « Syntaxe de Classe Imbriquée avec :: »class Bureaucrat {public: class GradeTooHighException : public std::exception { };};
throw Bureaucrat::GradeTooHighException();NomClasse::ClasseImbriqueeaccède à une classe définie à l’intérieur d’une autre classe::est l’opérateur de résolution de portée (comme avec les namespaces)- Les classes d’exception imbriquées gardent les exceptions reliées organisées
- La classe imbriquée peut être définie dans le header ou à l’extérieur
1. Le Problème Sans Exceptions
Section intitulée « 1. Le Problème Sans Exceptions »// Gestion d'erreur style Cint divide(int a, int b, int* result) { if (b == 0) return -1; // Code d'erreur *result = a / b; return 0; // Succès}
// L'appelant doit vérifier chaque valeur de retourint result;if (divide(10, 0, &result) != 0) { // Gérer l'erreur}// Facile d'oublier de vérifier !2. Gestion Basique des Exceptions
Section intitulée « 2. Gestion Basique des Exceptions »double divide(int a, int b) { if (b == 0) throw std::runtime_error("Division par zéro !"); return static_cast<double>(a) / b;}try/catch
Section intitulée « try/catch »try { double result = divide(10, 0); std::cout << "Résultat : " << result << std::endl;}catch (std::runtime_error& e) { std::cerr << "Erreur : " << e.what() << std::endl;}Blocs catch Multiples
Section intitulée « Blocs catch Multiples »try { // code qui pourrait lancer}catch (std::invalid_argument& e) { std::cerr << "Argument invalide : " << e.what() << std::endl;}catch (std::out_of_range& e) { std::cerr << "Hors limites : " << e.what() << std::endl;}catch (std::exception& e) { std::cerr << "Exception : " << e.what() << std::endl;}catch (...) { std::cerr << "Exception inconnue !" << std::endl;}3. Hiérarchie std::exception
Section intitulée « 3. Hiérarchie std::exception »std::exception├── std::logic_error│ ├── std::invalid_argument│ ├── std::domain_error│ ├── std::length_error│ └── std::out_of_range└── std::runtime_error ├── std::range_error └── std::overflow_errorLa Méthode what()
Section intitulée « La Méthode what() »class std::exception {public: virtual const char* what() const throw(); // Retourne le message d'erreur};4. Classes d’Exception Personnalisées
Section intitulée « 4. Classes d’Exception Personnalisées »Exception Personnalisée Basique
Section intitulée « Exception Personnalisée Basique »class GradeTooHighException : public std::exception {public: const char* what() const throw() { return "La note est trop haute !"; }};
// Utilisationif (grade < 1) throw GradeTooHighException();Classe d’Exception Imbriquée
Section intitulée « Classe d’Exception Imbriquée »class Bureaucrat {public: class GradeTooHighException : public std::exception { public: const char* what() const throw() { return "La note est trop haute !"; } };
class GradeTooLowException : public std::exception { public: const char* what() const throw() { return "La note est trop basse !"; } };};
// Utilisationthrow Bureaucrat::GradeTooHighException();Exception avec Message Dynamique
Section intitulée « Exception avec Message Dynamique »class FormException : public std::exception {private: std::string _message;public: FormException(const std::string& msg) : _message(msg) {} virtual ~FormException() throw() {}
const char* what() const throw() { return _message.c_str(); }};La Spécification d’Exception throw()
Section intitulée « La Spécification d’Exception throw() »En C++98, throw() déclare qu’une fonction promet de ne pas lancer d’exceptions :
// La fonction promet de ne pas lancervoid fonctionSure() throw() { // Si ceci lance, std::unexpected() est appelé -> terminate}
// La fonction peut lancer ces types spécifiquesvoid fonctionRisquee() throw(std::runtime_error, std::bad_alloc) { // Ne peut lancer que les types listés}
// Signature de what() - promet de ne pas lancerconst char* what() const throw();// ^^^^^^^ Spécification d'exceptionconst et throw() Ensemble
Section intitulée « const et throw() Ensemble »La méthode what() utilise les deux :
const char* what() const throw();// ^^^^^ ^^^^^^// | Spécification d'exception// fonction membre const (ne modifie pas l'objet)Pourquoi what() utilise throw() :
- Le code de gestion d’exception appelle
what()pour obtenir le message d’erreur - Si
what()elle-même lançait, cela causerait des problèmes pendant la gestion d’exception - La promesse de ne jamais lancer assure une récupération sûre du message d’erreur
5. Exercices du Module 05
Section intitulée « 5. Exercices du Module 05 »ex00 : Bureaucrat
Section intitulée « ex00 : Bureaucrat »class Bureaucrat {private: const std::string _name; int _grade; // 1 (le plus haut) à 150 (le plus bas)
public: class GradeTooHighException : public std::exception { public: const char* what() const throw(); };
class GradeTooLowException : public std::exception { public: const char* what() const throw(); };
Bureaucrat(const std::string& name, int grade); Bureaucrat(const Bureaucrat& other); Bureaucrat& operator=(const Bureaucrat& other); ~Bureaucrat();
const std::string& getName() const; int getGrade() const;
void incrementGrade(); // Lance si < 1 void decrementGrade(); // Lance si > 150};
// ImplémentationBureaucrat::Bureaucrat(const std::string& name, int grade) : _name(name), _grade(grade){ if (grade < 1) throw GradeTooHighException(); if (grade > 150) throw GradeTooLowException();}
void Bureaucrat::incrementGrade() { if (_grade - 1 < 1) throw GradeTooHighException(); _grade--;}
// Opérateur de fluxstd::ostream& operator<<(std::ostream& os, const Bureaucrat& b) { os << b.getName() << ", bureaucrat grade " << b.getGrade(); return os;}ex01 : Form
Section intitulée « ex01 : Form »class Form {private: const std::string _name; bool _signed; const int _gradeToSign; const int _gradeToExecute;
public: class GradeTooHighException : public std::exception { /* ... */ }; class GradeTooLowException : public std::exception { /* ... */ };
Form(const std::string& name, int signGrade, int execGrade); // ... OCF ...
const std::string& getName() const; bool isSigned() const; int getGradeToSign() const; int getGradeToExecute() const;
void beSigned(const Bureaucrat& b);};
// Dans la classe Bureaucrat :void Bureaucrat::signForm(Form& form) { try { form.beSigned(*this); std::cout << _name << " signed " << form.getName() << std::endl; } catch (std::exception& e) { std::cout << _name << " couldn't sign " << form.getName() << " because " << e.what() << std::endl; }}ex02 : Form Abstraite (AForm)
Section intitulée « ex02 : Form Abstraite (AForm) »class AForm {protected: // ... même que Form ... virtual void execute(Bureaucrat const& executor) const = 0; void checkExecutability(const Bureaucrat& executor) const;
public: // ... OCF ... void beSigned(const Bureaucrat& b);
class GradeTooHighException : public std::exception { /* ... */ }; class GradeTooLowException : public std::exception { /* ... */ }; class FormNotSignedException : public std::exception { /* ... */ };};
// Forms concrètesclass ShrubberyCreationForm : public AForm {public: ShrubberyCreationForm(const std::string& target); void execute(Bureaucrat const& executor) const;};
class RobotomyRequestForm : public AForm { // 50% de taux de succès};
class PresidentialPardonForm : public AForm { // Pardon par Zaphod Beeblebrox};ex03 : Intern (Pointeurs de Fonction pour Factory)
Section intitulée « ex03 : Intern (Pointeurs de Fonction pour Factory) »class Intern {public: Intern(); Intern(const Intern& other); Intern& operator=(const Intern& other); ~Intern();
AForm* makeForm(const std::string& formName, const std::string& target);};
// Fonctions helper qui créent chaque type de formstatic AForm* createShrubbery(const std::string& target) { return new ShrubberyCreationForm(target);}
static AForm* createRobotomy(const std::string& target) { return new RobotomyRequestForm(target);}
static AForm* createPresidential(const std::string& target) { return new PresidentialPardonForm(target);}
// Implémentation utilisant des pointeurs de fonction (pas de forêt if/else)AForm* Intern::makeForm(const std::string& formName, const std::string& target) { // Tableaux parallèles : noms et fonctions créatrices correspondantes std::string names[3] = { "shrubbery creation", "robotomy request", "presidential pardon" };
// Tableau de pointeurs de fonction // Type : pointeur vers fonction prenant const string& et retournant AForm* AForm* (*creators[3])(const std::string&) = { &createShrubbery, &createRobotomy, &createPresidential };
for (int i = 0; i < 3; i++) { if (formName == names[i]) { std::cout << "Intern creates " << formName << std::endl; return creators[i](target); // Appel via pointeur de fonction } }
std::cerr << "Form '" << formName << "' not found" << std::endl; return NULL;}Décomposition de la Syntaxe des Pointeurs de Fonction
Section intitulée « Décomposition de la Syntaxe des Pointeurs de Fonction »// Type de retour (*nom_pointeur)(types des paramètres)AForm* (*creator)(const std::string&);
// Tableau de pointeurs de fonctionAForm* (*creators[3])(const std::string&);
// Assigner une fonction au pointeurcreator = &createShrubbery; // ou juste : creator = createShrubbery;
// Appeler via le pointeurAForm* form = creator(target); // ou : (*creator)(target);6. I/O Fichier avec ofstream (ShrubberyCreationForm)
Section intitulée « 6. I/O Fichier avec ofstream (ShrubberyCreationForm) »Écrire dans des Fichiers
Section intitulée « Écrire dans des Fichiers »#include <fstream>
void ShrubberyCreationForm::execute(Bureaucrat const& executor) const { // Vérifier si la form peut être exécutée checkExecutability(executor);
// Créer fichier de sortie : target_shrubbery std::string filename = _target + "_shrubbery"; std::ofstream file(filename.c_str()); // .c_str() pour C++98
if (!file.is_open()) { throw std::runtime_error("Cannot create file"); }
// Écrire des arbres ASCII dans le fichier file << " _-_" << std::endl; file << " /~~ ~~\\" << std::endl; file << " /~~ ~~\\" << std::endl; file << "{ }" << std::endl; file << " \\ _- -_ /" << std::endl; file << " ~ \\\\ // ~" << std::endl; file << "_- - | | _- _" << std::endl; file << " _ - | | -_" << std::endl; file << " // \\\\" << std::endl;
file.close(); // Bonne pratique (se ferme aussi automatiquement à la destruction)}Méthodes ofstream
Section intitulée « Méthodes ofstream »std::ofstream file;
// Ouvrir fichierfile.open("filename.txt"); // Par défaut : tronquerfile.open("filename.txt", std::ios::app); // Mode ajout
// Vérifier si ouvertif (!file.is_open()) { /* erreur */ }if (file.fail()) { /* erreur */ }
// Écrirefile << "texte" << std::endl;file << 42 << std::endl;
// Fermerfile.close();7. Sécurité des Exceptions
Section intitulée « 7. Sécurité des Exceptions »Niveaux de Sécurité des Exceptions
Section intitulée « Niveaux de Sécurité des Exceptions »- Aucune garantie : Peut fuiter des ressources, laisser les objets dans un état invalide
- Garantie basique : Pas de fuites, objets dans un état valide (mais non spécifié)
- Garantie forte : L’opération réussit ou annule complètement
- Garantie no-throw : Ne lance jamais
Pattern RAII (Resource Acquisition Is Initialization)
Section intitulée « Pattern RAII (Resource Acquisition Is Initialization) »// MAUVAIS : Gestion manuelle des ressourcesvoid fonctionRisquee() { int* data = new int[100]; fairQuelqueChose(); // Si ceci lance, fuite mémoire ! delete[] data;}
// BON : RAII - ressource liée à la durée de vie de l'objetclass SafeArray {private: int* _data;public: SafeArray(int size) : _data(new int[size]) {} ~SafeArray() { delete[] _data; }};
void fonctionSure() { SafeArray data(100); fairQuelqueChose(); // Si ceci lance, le destructeur de SafeArray s'exécute quand même} // Nettoyage automatique8. Bonnes Pratiques
Section intitulée « 8. Bonnes Pratiques »// Attraper par référencecatch (std::exception& e) { /* ... */ }
// Utiliser des types d'exception spécifiquesthrow GradeTooHighException();
// Documenter les exceptions dans les commentaires/** @throws GradeTooHighException si grade < 1 */void setGrade(int grade);À NE PAS FAIRE
Section intitulée « À NE PAS FAIRE »// Ne pas attraper par valeur (slicing !)catch (std::exception e) { /* ... */ } // MAUVAIS
// Ne pas lancer des pointeursthrow new MyException(); // Risque de fuite mémoire
// Ne pas lancer dans les destructeurs~MyClass() { throw std::runtime_error("Erreur"); // Termine le programme !}Exercice 00 : Maman, Quand Je Serai Grand, Je Veux Être Bureaucrate !
Section intitulée « Exercice 00 : Maman, Quand Je Serai Grand, Je Veux Être Bureaucrate ! »Analyse du Sujet
Section intitulée « Analyse du Sujet »Créer une classe Bureaucrat avec :
- Nom : Constant, défini à la construction
- Grade : 1 (le plus haut) à 150 (le plus bas)
- Exceptions :
GradeTooHighExceptionetGradeTooLowException
Méthodes clés :
incrementGrade(): Rend le grade MEILLEUR (nombre plus bas)decrementGrade(): Rend le grade PIRE (nombre plus haut)- Les deux lancent si le grade deviendrait invalide
Stratégie d’Approche
Section intitulée « Stratégie d’Approche »Étape 1 - Comprendre le système de grades
- Grade 1 = Président (rang le plus haut)
- Grade 150 = Stagiaire (rang le plus bas)
- Incrémenter = promotion = le grade DESCEND
- Décrémenter = rétrogradation = le grade MONTE
Étape 2 - Créer des classes d’exception imbriquées
Les exceptions sont des classes imbriquées dans Bureaucrat qui héritent de std::exception.
Étape 3 - Lancer des exceptions pour les opérations invalides
Le constructeur et les changements de grade doivent valider.
Construction Progressive du Code
Section intitulée « Construction Progressive du Code »Étape 1 - Classes d’exception :
#ifndef BUREAUCRAT_HPP#define BUREAUCRAT_HPP
#include <string>#include <exception>#include <iostream>
class Bureaucrat {private: const std::string _name; int _grade; // 1 (meilleur) à 150 (pire)
public: // Classes d'exception imbriquées class GradeTooHighException : public std::exception { public: const char* what() const throw() { return "Grade is too high!"; } };
class GradeTooLowException : public std::exception { public: const char* what() const throw() { return "Grade is too low!"; } };
Bureaucrat(const std::string& name, int grade); Bureaucrat(const Bureaucrat& other); Bureaucrat& operator=(const Bureaucrat& other); ~Bureaucrat();
const std::string& getName() const; int getGrade() const;
void incrementGrade(); // Le grade DESCEND (meilleur) void decrementGrade(); // Le grade MONTE (pire)};
std::ostream& operator<<(std::ostream& os, const Bureaucrat& b);
#endifÉtape 2 - Constructeur avec validation :
Bureaucrat::Bureaucrat(const std::string& name, int grade) : _name(name), _grade(grade){ if (grade < 1) throw GradeTooHighException(); if (grade > 150) throw GradeTooLowException();}Étape 3 - Modification de grade avec exceptions :
void Bureaucrat::incrementGrade() { if (_grade - 1 < 1) throw GradeTooHighException(); _grade--; // Le grade DESCEND = meilleur}
void Bureaucrat::decrementGrade() { if (_grade + 1 > 150) throw GradeTooLowException(); _grade++; // Le grade MONTE = pire}Explication Ligne par Ligne
Section intitulée « Explication Ligne par Ligne »| Ligne | Code | Pourquoi |
|---|---|---|
const std::string _name | Membre const | Le nom ne peut pas changer après construction |
: public std::exception | Hériter | Permet d’attraper avec catch (std::exception&) |
const char* what() const throw() | Override | Interface d’exception standard |
throw GradeTooHighException() | Lancer exception | Créer et lancer l’objet exception |
_grade-- | Incrémenter = baisser | Grade 1 est le meilleur, donc “up” = nombre plus bas |
Pièges Courants
Section intitulée « Pièges Courants »1. Logique de grade inversée
// FAUX - incrémenter devrait rendre le grade MEILLEUR (plus bas)void Bureaucrat::incrementGrade() { _grade++; // Ceci rend le grade pire !}
// CORRECTvoid Bureaucrat::incrementGrade() { _grade--; // Nombre plus bas = meilleur grade}2. Classe d’exception n’héritant pas de std::exception
// FAUX - ne peut pas attraper avec std::exceptionclass GradeTooHighException { };
// CORRECT - hérite de std::exceptionclass GradeTooHighException : public std::exception { const char* what() const throw() { return "Grade is too high!"; }};3. Oublier de valider dans le constructeur
// FAUX - permet des grades invalidesBureaucrat::Bureaucrat(const std::string& name, int grade) : _name(name), _grade(grade) { }
// CORRECT - valider d'abordBureaucrat::Bureaucrat(const std::string& name, int grade) : _name(name), _grade(grade){ if (grade < 1) throw GradeTooHighException(); if (grade > 150) throw GradeTooLowException();}Conseils de Test
Section intitulée « Conseils de Test »int main() { try { Bureaucrat b1("Bob", 2); std::cout << b1 << std::endl; // "Bob, bureaucrat grade 2"
b1.incrementGrade(); // Le grade devient 1 std::cout << b1 << std::endl; // "Bob, bureaucrat grade 1"
b1.incrementGrade(); // Devrait lancer ! } catch (std::exception& e) { std::cerr << "Exception: " << e.what() << std::endl; }
try { Bureaucrat invalid("Bad", 0); // Devrait lancer } catch (std::exception& e) { std::cerr << "Exception: " << e.what() << std::endl; }
return 0;}Code Final
Section intitulée « Code Final »#include "Bureaucrat.hpp"
Bureaucrat::Bureaucrat(const std::string& name, int grade) : _name(name), _grade(grade){ if (grade < 1) throw GradeTooHighException(); if (grade > 150) throw GradeTooLowException();}
Bureaucrat::Bureaucrat(const Bureaucrat& other) : _name(other._name), _grade(other._grade) { }
Bureaucrat& Bureaucrat::operator=(const Bureaucrat& other) { if (this != &other) _grade = other._grade; // Ne peut pas changer _name (const) return *this;}
Bureaucrat::~Bureaucrat() { }
const std::string& Bureaucrat::getName() const { return _name; }int Bureaucrat::getGrade() const { return _grade; }
void Bureaucrat::incrementGrade() { if (_grade - 1 < 1) throw GradeTooHighException(); _grade--;}
void Bureaucrat::decrementGrade() { if (_grade + 1 > 150) throw GradeTooLowException(); _grade++;}
std::ostream& operator<<(std::ostream& os, const Bureaucrat& b) { os << b.getName() << ", bureaucrat grade " << b.getGrade(); return os;}Exercice 01 : En Rang, Les Recrues !
Section intitulée « Exercice 01 : En Rang, Les Recrues ! »Analyse du Sujet
Section intitulée « Analyse du Sujet »Créer une classe Form qui :
- A un nom, un statut de signature et deux exigences de grade
gradeToSign: Grade minimum nécessaire pour signergradeToExecute: Grade minimum nécessaire pour exécuter- Bureaucrat peut
signForm()- signe si le grade est suffisant
Stratégie d’Approche
Section intitulée « Stratégie d’Approche »Étape 1 - Comprendre la comparaison de grades
Rappelez-vous : nombre de grade plus bas = rang plus élevé
- Si le grade du Bureaucrat est 5 et le form exige 10 : OK (5 < 10)
- Si le grade du Bureaucrat est 15 et le form exige 10 : ÉCHEC (15 > 10)
Étape 2 - Ajouter des exceptions à Form
Form lance si le grade du bureaucrat est trop bas.
Étape 3 - Implémenter signForm dans Bureaucrat
signForm() essaie de signer et rapporte le succès/échec.
Construction Progressive du Code
Section intitulée « Construction Progressive du Code »Étape 1 - Classe Form :
#ifndef FORM_HPP#define FORM_HPP
#include <string>#include <exception>#include "Bureaucrat.hpp"
class Form {private: const std::string _name; bool _signed; const int _gradeToSign; const int _gradeToExecute;
public: class GradeTooHighException : public std::exception { public: const char* what() const throw() { return "Grade is too high!"; } };
class GradeTooLowException : public std::exception { public: const char* what() const throw() { return "Grade is too low!"; } };
Form(const std::string& name, int gradeToSign, int gradeToExecute); Form(const Form& other); Form& operator=(const Form& other); ~Form();
const std::string& getName() const; bool isSigned() const; int getGradeToSign() const; int getGradeToExecute() const;
void beSigned(const Bureaucrat& b); // Lance si grade trop bas};
std::ostream& operator<<(std::ostream& os, const Form& f);
#endifÉtape 2 - Implémentation de beSigned :
void Form::beSigned(const Bureaucrat& b) { if (b.getGrade() > _gradeToSign) // Nombre plus haut = rang plus bas throw GradeTooLowException(); _signed = true;}Étape 3 - signForm dans Bureaucrat :
void Bureaucrat::signForm(Form& f) { try { f.beSigned(*this); std::cout << _name << " signed " << f.getName() << std::endl; } catch (std::exception& e) { std::cout << _name << " couldn't sign " << f.getName() << " because " << e.what() << std::endl; }}Explication Ligne par Ligne
Section intitulée « Explication Ligne par Ligne »| Ligne | Code | Pourquoi |
|---|---|---|
const int _gradeToSign | Const | Les exigences ne changent pas |
b.getGrade() > _gradeToSign | Comparer grades | Nombre de grade plus haut = rang plus bas = ne peut pas signer |
f.beSigned(*this) | Passer soi-même | Bureaucrat se passe lui-même au Form |
catch (std::exception& e) | Attraper toute exception | Gérer l’échec de signature |
Pièges Courants
Section intitulée « Pièges Courants »1. Comparaison de grade inversée
// FAUX - ceci permet aux bureaucrats de bas rang de signer des forms de haut rang !if (b.getGrade() < _gradeToSign) throw GradeTooLowException();
// CORRECT - nombre de grade plus haut signifie rang plus BASif (b.getGrade() > _gradeToSign) throw GradeTooLowException();2. Mauvais format de sortie
// FAUX formatstd::cout << _name << " signs " << f.getName();
// CORRECT format (selon le sujet)std::cout << _name << " signed " << f.getName();// oustd::cout << _name << " couldn't sign " << f.getName() << " because " << reason;Conseils de Test
Section intitulée « Conseils de Test »int main() { Bureaucrat boss("Boss", 1); Bureaucrat intern("Intern", 150); Form importantForm("TPS Report", 50, 25);
std::cout << importantForm << std::endl;
intern.signForm(importantForm); // Devrait échouer boss.signForm(importantForm); // Devrait réussir
std::cout << importantForm << std::endl;
return 0;}Code Final
Section intitulée « Code Final »#include "Form.hpp"
Form::Form(const std::string& name, int gradeToSign, int gradeToExecute) : _name(name), _signed(false), _gradeToSign(gradeToSign), _gradeToExecute(gradeToExecute){ if (gradeToSign < 1 || gradeToExecute < 1) throw GradeTooHighException(); if (gradeToSign > 150 || gradeToExecute > 150) throw GradeTooLowException();}
Form::Form(const Form& other) : _name(other._name), _signed(other._signed), _gradeToSign(other._gradeToSign), _gradeToExecute(other._gradeToExecute) { }
Form& Form::operator=(const Form& other) { if (this != &other) _signed = other._signed; // Seul le statut de signature peut changer return *this;}
Form::~Form() { }
const std::string& Form::getName() const { return _name; }bool Form::isSigned() const { return _signed; }int Form::getGradeToSign() const { return _gradeToSign; }int Form::getGradeToExecute() const { return _gradeToExecute; }
void Form::beSigned(const Bureaucrat& b) { if (b.getGrade() > _gradeToSign) throw GradeTooLowException(); _signed = true;}
std::ostream& operator<<(std::ostream& os, const Form& f) { os << "Form " << f.getName() << " [signed: " << (f.isSigned() ? "yes" : "no") << ", sign grade: " << f.getGradeToSign() << ", exec grade: " << f.getGradeToExecute() << "]"; return os;}Exercice 02 : Non, Il Vous Faut le Formulaire 28B, Pas le 28C…
Section intitulée « Exercice 02 : Non, Il Vous Faut le Formulaire 28B, Pas le 28C… »Analyse du Sujet
Section intitulée « Analyse du Sujet »Rendre Form abstraite (AForm) et créer des forms concrètes :
- ShrubberyCreationForm : Crée des arbres ASCII dans un fichier
- RobotomyRequestForm : 50% de chance de succès
- PresidentialPardonForm : Pardonne la cible
Chaque form a des exigences de grade spécifiques et une méthode execute().
Stratégie d’Approche
Section intitulée « Stratégie d’Approche »Étape 1 - Rendre Form abstraite
Renommer en AForm, ajouter execute() virtuelle pure.
Étape 2 - Créer des forms concrètes
Chacune hérite de AForm et implémente execute().
Étape 3 - Vérifier les préconditions dans execute
- La form doit être signée
- L’exécuteur doit avoir un grade suffisant
Construction Progressive du Code
Section intitulée « Construction Progressive du Code »Étape 1 - Form Abstraite :
class AForm {// ... même que Form, mais ajouter :public: class FormNotSignedException : public std::exception { public: const char* what() const throw() { return "Form not signed!"; } };
virtual void execute(Bureaucrat const& executor) const = 0; // Virtuelle pure
protected: void checkExecution(Bureaucrat const& executor) const; // Helper};Étape 2 - Helper de vérification d’exécution :
void AForm::checkExecution(Bureaucrat const& executor) const { if (!_signed) throw FormNotSignedException(); if (executor.getGrade() > _gradeToExecute) throw GradeTooLowException();}Étape 3 - Form concrète (ShrubberyCreationForm) :
#ifndef SHRUBBERYCREATIONFORM_HPP#define SHRUBBERYCREATIONFORM_HPP
#include "AForm.hpp"
class ShrubberyCreationForm : public AForm {private: std::string _target;
public: ShrubberyCreationForm(const std::string& target); ShrubberyCreationForm(const ShrubberyCreationForm& other); ShrubberyCreationForm& operator=(const ShrubberyCreationForm& other); ~ShrubberyCreationForm();
void execute(Bureaucrat const& executor) const;};
#endif#include "ShrubberyCreationForm.hpp"#include <fstream>
// Grade pour signer : 145, Grade pour exécuter : 137ShrubberyCreationForm::ShrubberyCreationForm(const std::string& target) : AForm("ShrubberyCreationForm", 145, 137), _target(target) { }
void ShrubberyCreationForm::execute(Bureaucrat const& executor) const { checkExecution(executor); // Lance si non autorisé
std::ofstream file((_target + "_shrubbery").c_str()); file << " _-_" << std::endl; file << " /~~ ~~\\" << std::endl; file << " { @ @ }" << std::endl; file << " \\ _ /" << std::endl; file << " `\\ /`" << std::endl; file << " | |" << std::endl; file << " /||\\" << std::endl; file.close();}Étape 4 - RobotomyRequestForm :
#include "RobotomyRequestForm.hpp"#include <cstdlib>#include <ctime>
// Grade pour signer : 72, Grade pour exécuter : 45RobotomyRequestForm::RobotomyRequestForm(const std::string& target) : AForm("RobotomyRequestForm", 72, 45), _target(target) { }
void RobotomyRequestForm::execute(Bureaucrat const& executor) const { checkExecution(executor);
std::cout << "* DRILLING NOISES *" << std::endl;
if (std::rand() % 2) std::cout << _target << " has been robotomized successfully!" << std::endl; else std::cout << "Robotomy of " << _target << " failed!" << std::endl;}Étape 5 - Ajouter executeForm à Bureaucrat :
void Bureaucrat::executeForm(AForm const& form) { try { form.execute(*this); std::cout << _name << " executed " << form.getName() << std::endl; } catch (std::exception& e) { std::cout << _name << " couldn't execute " << form.getName() << " because " << e.what() << std::endl; }}Explication Ligne par Ligne
Section intitulée « Explication Ligne par Ligne »| Ligne | Code | Pourquoi |
|---|---|---|
virtual void execute(...) const = 0 | Virtuelle pure | Rend AForm abstraite |
checkExecution(executor) | Appel helper | Validation réutilisable |
std::rand() % 2 | Chance 50/50 | Succès aléatoire pour Robotomy |
.c_str() | String vers C-string | Les flux de fichiers C++98 ont besoin de const char* |
Pièges Courants
Section intitulée « Pièges Courants »1. Oublier de vérifier les préconditions
// FAUX - pas de validation !void ShrubberyCreationForm::execute(Bureaucrat const& executor) const { // Écrire le fichier...}
// CORRECT - valider d'abordvoid ShrubberyCreationForm::execute(Bureaucrat const& executor) const { checkExecution(executor); // Peut lancer // Écrire le fichier...}2. Ne pas initialiser le random
int main() { std::srand(std::time(NULL)); // Initialiser le générateur de nombres aléatoires // ...}Conseils de Test
Section intitulée « Conseils de Test »int main() { std::srand(std::time(NULL));
Bureaucrat president("President", 1); Bureaucrat clerk("Clerk", 140);
ShrubberyCreationForm shrub("home"); RobotomyRequestForm robot("Bender"); PresidentialPardonForm pardon("Ford");
// Essayer d'exécuter des forms non signées president.executeForm(shrub); // Échoue : non signée
// Signer et exécuter president.signForm(shrub); president.executeForm(shrub); // Crée le fichier home_shrubbery
clerk.signForm(robot); // Échoue : grade trop bas president.signForm(robot); president.executeForm(robot); // 50% de succès
return 0;}Code Final
Section intitulée « Code Final »#include "PresidentialPardonForm.hpp"
// Grade pour signer : 25, Grade pour exécuter : 5PresidentialPardonForm::PresidentialPardonForm(const std::string& target) : AForm("PresidentialPardonForm", 25, 5), _target(target) { }
PresidentialPardonForm::PresidentialPardonForm(const PresidentialPardonForm& other) : AForm(other), _target(other._target) { }
PresidentialPardonForm& PresidentialPardonForm::operator=(const PresidentialPardonForm& other) { AForm::operator=(other); _target = other._target; return *this;}
PresidentialPardonForm::~PresidentialPardonForm() { }
void PresidentialPardonForm::execute(Bureaucrat const& executor) const { checkExecution(executor); std::cout << _target << " has been pardoned by Zaphod Beeblebrox." << std::endl;}Exercice 03 : Au Moins C’est Mieux que de Faire le Café
Section intitulée « Exercice 03 : Au Moins C’est Mieux que de Faire le Café »Analyse du Sujet
Section intitulée « Analyse du Sujet »Créer une classe Intern qui peut créer des forms en utilisant une méthode factory :
AForm* makeForm(const std::string& name, const std::string& target);Attente du sujet : eviter une grosse chaine if/else if/else (considere comme brouillon et refusee a l’evaluation). Utilisez plutot une table de correspondance (pointeurs de fonction ou similaire).
Stratégie d’Approche
Section intitulée « Stratégie d’Approche »Étape 1 - Définir le type de fonction créatrice de form
typedef AForm* (*FormCreator)(const std::string&);Étape 2 - Créer des fonctions créatrices statiques
Une fonction par type de form.
Étape 3 - Utiliser des tableaux parallèles
Tableau de noms + tableau de créateurs, boucler et matcher.
Construction Progressive du Code
Section intitulée « Construction Progressive du Code »Étape 1 - Classe Intern :
#ifndef INTERN_HPP#define INTERN_HPP
#include "AForm.hpp"
class Intern {public: Intern(); Intern(const Intern& other); Intern& operator=(const Intern& other); ~Intern();
AForm* makeForm(const std::string& name, const std::string& target);};
#endifÉtape 2 - Implémentation Factory :
#include "Intern.hpp"#include "ShrubberyCreationForm.hpp"#include "RobotomyRequestForm.hpp"#include "PresidentialPardonForm.hpp"#include <iostream>
// Type de pointeur de fonctiontypedef AForm* (*FormCreator)(const std::string&);
// Fonctions créatricesstatic AForm* createShrubbery(const std::string& target) { return new ShrubberyCreationForm(target);}
static AForm* createRobotomy(const std::string& target) { return new RobotomyRequestForm(target);}
static AForm* createPresidential(const std::string& target) { return new PresidentialPardonForm(target);}
// Méthode factoryAForm* Intern::makeForm(const std::string& name, const std::string& target) { // Tableaux parallèles - PAS de if/else ! std::string names[] = { "shrubbery creation", "robotomy request", "presidential pardon" }; FormCreator creators[] = { createShrubbery, createRobotomy, createPresidential };
for (int i = 0; i < 3; i++) { if (name == names[i]) { std::cout << "Intern creates " << name << std::endl; return creators[i](target); } }
std::cerr << "Intern couldn't create form: " << name << " not found" << std::endl; return NULL;}Explication Ligne par Ligne
Section intitulée « Explication Ligne par Ligne »| Ligne | Code | Pourquoi |
|---|---|---|
typedef AForm* (*FormCreator)(...) | Type pointeur fonction | Définit le type pour les fonctions créatrices |
static AForm* createShrubbery(...) | Helper statique | Pas un membre, juste une fonction helper |
creators[i](target) | Appel via pointeur | Invoque la fonction créatrice |
return NULL | Form inconnue | Gestion gracieuse des noms invalides |
Pièges Courants
Section intitulée « Pièges Courants »1. Utiliser une grosse chaine if/else if (refusee a l’evaluation)
// FAUX : difficile a lire/maintenir (le sujet met en garde contre les chaines if/else "messy")if (name == "shrubbery creation") return new ShrubberyCreationForm(target);else if (name == "robotomy request") return new RobotomyRequestForm(target);// ...
// CORRECT - utiliser des pointeurs de fonctionfor (int i = 0; i < 3; i++) { if (name == names[i]) return creators[i](target);}2. Oublier la propriété de la mémoire
// L'appelant possède le pointeur retourné !AForm* form = intern.makeForm("robotomy request", "Bob");// ... utiliser form ...delete form; // L'appelant doit delete !Conseils de Test
Section intitulée « Conseils de Test »int main() { Intern someRandomIntern; Bureaucrat boss("Boss", 1);
AForm* form1 = someRandomIntern.makeForm("shrubbery creation", "garden"); AForm* form2 = someRandomIntern.makeForm("robotomy request", "Bender"); AForm* form3 = someRandomIntern.makeForm("presidential pardon", "Ford"); AForm* form4 = someRandomIntern.makeForm("invalid form", "test"); // NULL
if (form1) { boss.signForm(*form1); boss.executeForm(*form1); delete form1; } if (form2) { boss.signForm(*form2); boss.executeForm(*form2); delete form2; } if (form3) { boss.signForm(*form3); boss.executeForm(*form3); delete form3; }
return 0;}Code Final
Section intitulée « Code Final »#include "Intern.hpp"#include "ShrubberyCreationForm.hpp"#include "RobotomyRequestForm.hpp"#include "PresidentialPardonForm.hpp"#include <iostream>
Intern::Intern() { }Intern::Intern(const Intern& other) { (void)other; }Intern& Intern::operator=(const Intern& other) { (void)other; return *this; }Intern::~Intern() { }
typedef AForm* (*FormCreator)(const std::string&);
static AForm* createShrubbery(const std::string& target) { return new ShrubberyCreationForm(target);}
static AForm* createRobotomy(const std::string& target) { return new RobotomyRequestForm(target);}
static AForm* createPresidential(const std::string& target) { return new PresidentialPardonForm(target);}
AForm* Intern::makeForm(const std::string& name, const std::string& target) { std::string names[] = { "shrubbery creation", "robotomy request", "presidential pardon" }; FormCreator creators[] = { createShrubbery, createRobotomy, createPresidential };
for (int i = 0; i < 3; i++) { if (name == names[i]) { std::cout << "Intern creates " << name << std::endl; return creators[i](target); } }
std::cerr << "Intern couldn't create form: " << name << " not found" << std::endl; return NULL;}Référence Rapide
Section intitulée « Référence Rapide »Syntaxe d’Exception
Section intitulée « Syntaxe d’Exception »throw ExceptionType(); // Lancer exceptionthrow ExceptionType("message"); // Avec message
try { /* code risqué */ }catch (Type& e) { /* gérer */ }catch (...) { /* tout attraper */ }Template d’Exception Personnalisée
Section intitulée « Template d’Exception Personnalisée »class MyException : public std::exception {public: const char* what() const throw() { return "Mon message d'erreur"; }};Règles 42
Section intitulée « Règles 42 »- Les classes d’exception N’ONT PAS besoin d’OCF
- Toutes les autres classes DOIVENT suivre l’OCF
- Utilisez des classes imbriquées pour les exceptions reliées
Concepts Reliés
Section intitulée « Concepts Reliés »Continuez votre voyage C++ :
- Précédent : Module 04 : Polymorphisme - Revoir les fonctions virtuelles et classes abstraites
- Suivant : Module 06 : Casts C++ - Apprendre les opérateurs de cast type-safe
Termes Clés de Ce Module
Section intitulée « Termes Clés de Ce Module »Visitez le Glossaire pour les définitions de :