Aller au contenu

Module 05 : Exceptions

Télécharger le PDF officiel du sujet

Concepts Clés :

  • Mots-clés try, catch, throw
  • Classes d’exceptions
  • Hiérarchie std::exception
  • Exceptions imbriquées
  • Sécurité des exceptions

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.


Ce module introduit la syntaxe de gestion des exceptions. Voici ce que fait chaque mot-clé :

throw std::runtime_error("Quelque chose s'est mal passé !");
  • throw arrê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
try {
// Code qui pourrait lancer
fonctionRisquee();
} catch (...) { }
  • try définit une “zone protégée” où les exceptions peuvent être attrapées
  • Toute exception lancée dans un bloc try sera attrapée par un catch correspondant
  • Sans try, une exception terminerait le programme
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)
}
  • catch spé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
const char* what() const throw(); // Promet de ne pas lancer
void fonctionRisquee() throw(std::runtime_error); // Ne peut lancer que les types listés
  • throw() 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() utilise throw() 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
class Bureaucrat {
public:
class GradeTooHighException : public std::exception { };
};
throw Bureaucrat::GradeTooHighException();
  • NomClasse::ClasseImbriquee accè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

// Gestion d'erreur style C
int 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 retour
int result;
if (divide(10, 0, &result) != 0) {
// Gérer l'erreur
}
// Facile d'oublier de vérifier !

double divide(int a, int b) {
if (b == 0)
throw std::runtime_error("Division par zéro !");
return static_cast<double>(a) / b;
}
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;
}
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;
}

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_error
class std::exception {
public:
virtual const char* what() const throw(); // Retourne le message d'erreur
};

class GradeTooHighException : public std::exception {
public:
const char* what() const throw() {
return "La note est trop haute !";
}
};
// Utilisation
if (grade < 1)
throw GradeTooHighException();
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 !";
}
};
};
// Utilisation
throw Bureaucrat::GradeTooHighException();
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();
}
};

En C++98, throw() déclare qu’une fonction promet de ne pas lancer d’exceptions :

// La fonction promet de ne pas lancer
void fonctionSure() throw() {
// Si ceci lance, std::unexpected() est appelé -> terminate
}
// La fonction peut lancer ces types spécifiques
void fonctionRisquee() throw(std::runtime_error, std::bad_alloc) {
// Ne peut lancer que les types listés
}
// Signature de what() - promet de ne pas lancer
const char* what() const throw();
// ^^^^^^^ Spécification d'exception

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

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émentation
Bureaucrat::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 flux
std::ostream& operator<<(std::ostream& os, const Bureaucrat& b) {
os << b.getName() << ", bureaucrat grade " << b.getGrade();
return os;
}
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;
}
}
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ètes
class 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 form
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);
}
// 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 fonction
AForm* (*creators[3])(const std::string&);
// Assigner une fonction au pointeur
creator = &createShrubbery; // ou juste : creator = createShrubbery;
// Appeler via le pointeur
AForm* form = creator(target); // ou : (*creator)(target);

6. I/O Fichier avec ofstream (ShrubberyCreationForm)

Section intitulée « 6. I/O Fichier avec ofstream (ShrubberyCreationForm) »
#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)
}
std::ofstream file;
// Ouvrir fichier
file.open("filename.txt"); // Par défaut : tronquer
file.open("filename.txt", std::ios::app); // Mode ajout
// Vérifier si ouvert
if (!file.is_open()) { /* erreur */ }
if (file.fail()) { /* erreur */ }
// Écrire
file << "texte" << std::endl;
file << 42 << std::endl;
// Fermer
file.close();

  1. Aucune garantie : Peut fuiter des ressources, laisser les objets dans un état invalide
  2. Garantie basique : Pas de fuites, objets dans un état valide (mais non spécifié)
  3. Garantie forte : L’opération réussit ou annule complètement
  4. 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 ressources
void 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'objet
class 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 automatique

// Attraper par référence
catch (std::exception& e) { /* ... */ }
// Utiliser des types d'exception spécifiques
throw GradeTooHighException();
// Documenter les exceptions dans les commentaires
/** @throws GradeTooHighException si grade < 1 */
void setGrade(int grade);
// Ne pas attraper par valeur (slicing !)
catch (std::exception e) { /* ... */ } // MAUVAIS
// Ne pas lancer des pointeurs
throw 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 ! »

Créer une classe Bureaucrat avec :

  • Nom : Constant, défini à la construction
  • Grade : 1 (le plus haut) à 150 (le plus bas)
  • Exceptions : GradeTooHighException et GradeTooLowException

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

É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.

Étape 1 - Classes d’exception :

Bureaucrat.hpp
#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.cpp
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 :

Bureaucrat.cpp
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
}
LigneCodePourquoi
const std::string _nameMembre constLe nom ne peut pas changer après construction
: public std::exceptionHériterPermet d’attraper avec catch (std::exception&)
const char* what() const throw()OverrideInterface d’exception standard
throw GradeTooHighException()Lancer exceptionCréer et lancer l’objet exception
_grade--Incrémenter = baisserGrade 1 est le meilleur, donc “up” = nombre plus bas

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 !
}
// CORRECT
void 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::exception
class GradeTooHighException { };
// CORRECT - hérite de std::exception
class 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 invalides
Bureaucrat::Bureaucrat(const std::string& name, int grade)
: _name(name), _grade(grade) { }
// CORRECT - valider d'abord
Bureaucrat::Bureaucrat(const std::string& name, int grade)
: _name(name), _grade(grade)
{
if (grade < 1) throw GradeTooHighException();
if (grade > 150) throw GradeTooLowException();
}
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;
}
Bureaucrat.cpp
#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;
}

Créer une classe Form qui :

  • A un nom, un statut de signature et deux exigences de grade
  • gradeToSign : Grade minimum nécessaire pour signer
  • gradeToExecute : Grade minimum nécessaire pour exécuter
  • Bureaucrat peut signForm() - signe si le grade est suffisant

É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.

Étape 1 - Classe Form :

Form.hpp
#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 :

Form.cpp
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 :

Bureaucrat.cpp
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;
}
}
LigneCodePourquoi
const int _gradeToSignConstLes exigences ne changent pas
b.getGrade() > _gradeToSignComparer gradesNombre de grade plus haut = rang plus bas = ne peut pas signer
f.beSigned(*this)Passer soi-mêmeBureaucrat se passe lui-même au Form
catch (std::exception& e)Attraper toute exceptionGérer l’échec de signature

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 BAS
if (b.getGrade() > _gradeToSign)
throw GradeTooLowException();

2. Mauvais format de sortie

// FAUX format
std::cout << _name << " signs " << f.getName();
// CORRECT format (selon le sujet)
std::cout << _name << " signed " << f.getName();
// ou
std::cout << _name << " couldn't sign " << f.getName() << " because " << reason;
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;
}
Form.cpp
#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… »

Rendre Form abstraite (AForm) et créer des forms concrètes :

  1. ShrubberyCreationForm : Crée des arbres ASCII dans un fichier
  2. RobotomyRequestForm : 50% de chance de succès
  3. PresidentialPardonForm : Pardonne la cible

Chaque form a des exigences de grade spécifiques et une méthode execute().

É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

Étape 1 - Form Abstraite :

AForm.hpp
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 :

AForm.cpp
void AForm::checkExecution(Bureaucrat const& executor) const {
if (!_signed)
throw FormNotSignedException();
if (executor.getGrade() > _gradeToExecute)
throw GradeTooLowException();
}

Étape 3 - Form concrète (ShrubberyCreationForm) :

ShrubberyCreationForm.hpp
#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
ShrubberyCreationForm.cpp
#include "ShrubberyCreationForm.hpp"
#include <fstream>
// Grade pour signer : 145, Grade pour exécuter : 137
ShrubberyCreationForm::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 :

RobotomyRequestForm.cpp
#include "RobotomyRequestForm.hpp"
#include <cstdlib>
#include <ctime>
// Grade pour signer : 72, Grade pour exécuter : 45
RobotomyRequestForm::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 :

Bureaucrat.cpp
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;
}
}
LigneCodePourquoi
virtual void execute(...) const = 0Virtuelle pureRend AForm abstraite
checkExecution(executor)Appel helperValidation réutilisable
std::rand() % 2Chance 50/50Succès aléatoire pour Robotomy
.c_str()String vers C-stringLes flux de fichiers C++98 ont besoin de const char*

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'abord
void 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
// ...
}
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;
}
PresidentialPardonForm.cpp
#include "PresidentialPardonForm.hpp"
// Grade pour signer : 25, Grade pour exécuter : 5
PresidentialPardonForm::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é »

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).

É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.

Étape 1 - Classe Intern :

Intern.hpp
#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 :

Intern.cpp
#include "Intern.hpp"
#include "ShrubberyCreationForm.hpp"
#include "RobotomyRequestForm.hpp"
#include "PresidentialPardonForm.hpp"
#include <iostream>
// Type de pointeur de fonction
typedef AForm* (*FormCreator)(const std::string&);
// Fonctions créatrices
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);
}
// Méthode factory
AForm* 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;
}
LigneCodePourquoi
typedef AForm* (*FormCreator)(...)Type pointeur fonctionDéfinit le type pour les fonctions créatrices
static AForm* createShrubbery(...)Helper statiquePas un membre, juste une fonction helper
creators[i](target)Appel via pointeurInvoque la fonction créatrice
return NULLForm inconnueGestion gracieuse des noms invalides

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 fonction
for (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 !
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;
}
Intern.cpp
#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;
}

throw ExceptionType(); // Lancer exception
throw ExceptionType("message"); // Avec message
try { /* code risqué */ }
catch (Type& e) { /* gérer */ }
catch (...) { /* tout attraper */ }
class MyException : public std::exception {
public:
const char* what() const throw() {
return "Mon message d'erreur";
}
};
  • 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

Continuez votre voyage C++ :

Visitez le Glossaire pour les définitions de :