Module 03 : Héritage
Concepts clés :
- Classes de base et dérivées
- Spécificateurs d’accès avec l’héritage
- Chaînage des constructeurs/destructeurs
- Accès aux membres dans l’héritage
- Héritage multiple (Problème du Diamant)
Pourquoi c’est important
Section intitulée « Pourquoi c’est important »L’héritage est le fondement de la conception orientée objet. Il permet la réutilisation du code et établit des relations entre les types.
Pourquoi l’héritage ? (Le problème : Duplication du code)
Section intitulée « Pourquoi l’héritage ? (Le problème : Duplication du code) »Imaginez que vous écrivez un jeu avec différents types de robots :
// Sans héritage - beaucoup de code dupliqué !class ClapTrap { std::string _name; int _hitPoints; int _energyPoints; void attack(std::string target); void takeDamage(int amount); void beRepaired(int amount);};
class ScavTrap { std::string _name; // Dupliqué ! int _hitPoints; // Dupliqué ! int _energyPoints; // Dupliqué ! void attack(std::string target); // Logique similaire ! void takeDamage(int amount); // Dupliqué ! void beRepaired(int amount); // Dupliqué ! void guardGate(); // Seulement ceci est unique};
class FragTrap { std::string _name; // Dupliqué ENCORE ! // ... même problème de duplication};C’est pénible :
- Même code écrit plusieurs fois
- Les corrections de bugs doivent être appliquées à plusieurs endroits
- Ajouter des fonctionnalités signifie modifier de nombreuses classes
L’héritage résout cela : Extraire le code commun dans une classe de base, puis dériver des classes spécialisées qui ajoutent des fonctionnalités uniques :
class ClapTrap { /* code commun */ };class ScavTrap : public ClapTrap { void guardGate(); }; // Ajoute guardGateclass FragTrap : public ClapTrap { void highFivesGuys(); }; // Ajoute highFivesComprendre la syntaxe de l’héritage (Pour débutants)
Section intitulée « Comprendre la syntaxe de l’héritage (Pour débutants) »Ce module introduit l’héritage de classes. Voici les nouveaux opérateurs et syntaxes que vous rencontrerez :
L’opérateur : (Déclaration d’héritage)
Section intitulée « L’opérateur : (Déclaration d’héritage) »class Derived : public Base { }; // Derived hérite de Base:(deux-points) introduit la liste d’héritage- Cela signifie « cette classe EST-UN type de cette classe »
publicsignifie que les membres public de la classe de base restent public dans la classe dérivée- Lisez-le comme : « Derived hérite publiquement de Base »
Le mot-clé virtual (Modificateur d’héritage)
Section intitulée « Le mot-clé virtual (Modificateur d’héritage) »class Middle : virtual public Base { }; // Héritage virtuelvirtualdans l’héritage résout le « problème du diamant »- Sans
virtual, une classe héritant de deux classes qui partagent une base obtient DEUX copies de la base - Avec
virtual, seulement UNE copie de la classe de base existe - Utilisez ceci quand l’héritage multiple crée une forme de diamant dans la hiérarchie des classes
Accéder aux membres de la classe de base avec ::
Section intitulée « Accéder aux membres de la classe de base avec :: »ClapTrap::attack(target); // Appeler la version de la classe de baseBase::operator=(other); // Appeler l'opérateur d'assignation de la baseClassName::vous permet d’appeler explicitement une méthode de la classe de base- C’est nécessaire quand vous avez redéfini (overridé) une méthode dans la classe dérivée
- Utilisez-le pour accéder à l’implémentation de la classe de base depuis la classe dérivée
La déclaration using
Section intitulée « La déclaration using »using ScavTrap::attack; // Amener attack de ScavTrap dans la portée couranteusing Base::print; // Amener TOUTES les surcharges de Base::print dans la portéeusingimporte des noms d’une autre portée dans la portée courante- Dans l’héritage, cela amène les méthodes de la classe de base dans la portée de la classe dérivée
- Utile quand vous redéfinissez certaines surcharges mais voulez garder les autres accessibles
- Sans
using Base::print, si vous redéfinissezprint()(sans paramètres), la surchargeprint(int)devient masquée
Le spécificateur d’accès protected
Section intitulée « Le spécificateur d’accès protected »class Base {protected: // Accessible dans cette classe ET les classes dérivées int _hitPoints;public: // Accessible partout void attack();};protectedest entreprivateetpublic- Private : Seule la classe elle-même peut y accéder
- Protected : La classe ET ses classes dérivées peuvent y accéder
- Public : Tout le monde peut y accéder
- Utilisez
protectedpour les membres que les classes dérivées doivent utiliser mais que les extérieurs ne devraient pas accéder
Chaînage des constructeurs avec liste d’initialisation
Section intitulée « Chaînage des constructeurs avec liste d’initialisation »ScavTrap(std::string name) : ClapTrap(name) { // Constructeur de base appelé en premier, puis ceci s'exécute}- La liste d’initialisation (
:avant le{) chaîne vers le constructeur de base ClapTrap(name)appelle le constructeur de la classe de base avec le paramètre name- Le constructeur de la classe de base s’exécute AVANT le corps du constructeur de la classe dérivée
- Vous DEVEZ appeler le constructeur de base si la base n’a pas de constructeur par défaut
Pourquoi la base s’exécute en premier ? Un objet dérivé contient l’objet de base « à l’intérieur ». La partie base doit être entièrement construite avant que la partie dérivée puisse l’utiliser. Pensez à construire une maison : vous avez besoin des fondations (base) avant de pouvoir ajouter les étages supérieurs (dérivée).
Appeler le constructeur de copie de base
Section intitulée « Appeler le constructeur de copie de base »ScavTrap(const ScavTrap& other) : ClapTrap(other) { // other est un ScavTrap, qui EST-UN ClapTrap}ClapTrap(other)appelle le constructeur de copie de base- Même si
otherest unScavTrap, il peut être passé commeClapTrap&car la relation d’héritage EST-UN - Cela copie les membres de la classe de base ; vous copiez les membres de la classe dérivée dans le corps
1. Héritage de base
Section intitulée « 1. Héritage de base »class Base {protected: std::string _name; int _hitPoints;
public: Base(std::string name); void attack(const std::string& target);};
class Derived : public ClapTrap { // Derived hérite de ClapTrappublic: Derived(std::string name); void specialAbility();};La relation « Est-Un »
Section intitulée « La relation « Est-Un » »┌─────────────────────────────────────────────────────────────────┐│ Héritage = Relation "EST-UN" │├─────────────────────────────────────────────────────────────────┤│ ││ Modèle visuel : ││ ┌───────────────┐ ││ │ ClapTrap │ ←── Classe de base (parent) ││ │ ───────── │ Définit les attributs communs ││ │ _name │ et les comportements ││ │ _hitPoints │ ││ │ attack() │ ││ └───────┬───────┘ ││ │ ││ │ "EST-UN" ││ │ ││ ┌────┴────┐ ││ ▼ ▼ ││ ┌────────┐ ┌────────┐ ││ │ScavTrap│ │FragTrap│ ←── Classes dérivées (enfants) ││ │ EST-UN │ │ EST-UN │ Héritent de ClapTrap ││ │ClapTrap│ │ClapTrap│ + ajoutent des spécialisations ││ │────────│ │────────│ ││ │guardG- │ │highF- │ ││ │ate() │ │ives() │ ││ └────────┘ └────────┘ ││ ││ Disposition mémoire : ││ ┌──────────────────────────────────────────────────────┐ ││ │ Partie ClapTrap (héritée) │ ││ │ ┌─────────────┐ │ ││ │ │ _name │ │ ││ │ │ _hitPoints │ │ ││ │ │ _energy │ ←── Données de la classe de base │ ││ │ └─────────────┘ │ ││ ├──────────────────────────────────────────────────────┤ ││ │ Partie ScavTrap (ajoutée) │ ││ │ ┌─────────────┐ │ ││ │ │ _gateMode │ ←── Données de la classe dérivée │ ││ │ └─────────────┘ │ ││ └──────────────────────────────────────────────────────┘ ││ ││ Lisez-le comme : "ScavTrap EST-UN ClapTrap" ││ - Un ScavTrap peut faire tout ce qu'un ClapTrap peut faire ││ - Plus ScavTrap ajoute ses propres capacités spéciales ││ - Utilisez l'héritage pour les relations "est-un" ││ - Utilisez la composition pour les relations "a-un" ││ │└─────────────────────────────────────────────────────────────────┘Qu’est-ce qui est hérité ?
Section intitulée « Qu’est-ce qui est hérité ? »| Type de membre | Hérité ? |
|---|---|
| Membres public | Oui (comme public) |
| Membres protected | Oui (comme protected) |
| Membres private | Non (existent mais inaccessibles) |
| Constructeurs | Non (mais peuvent être appelés) |
| Destructeurs | Non (mais automatiquement appelés) |
2. Spécificateurs d’accès dans l’héritage
Section intitulée « 2. Spécificateurs d’accès dans l’héritage »Héritage public (Le plus courant)
Section intitulée « Héritage public (Le plus courant) »class Derived : public Base { // Base public -> Derived public // Base protected -> Derived protected // Base private -> inaccessible};Héritage protected
Section intitulée « Héritage protected »class Derived : protected Base { // Base public -> Derived protected // Base protected -> Derived protected // Base private -> inaccessible};Héritage private
Section intitulée « Héritage private »class Derived : private Base { // Base public -> Derived private // Base protected -> Derived private // Base private -> inaccessible};Résumé visuel
Section intitulée « Résumé visuel » Dans classe dérivée À l'extérieurHéritage public : Base public public accessible Base protected protected non accessible Base private - -
Héritage protected : Base public protected non accessible Base protected protected non accessible Base private - -
Héritage private : Base public private non accessible Base protected private non accessible Base private - -3. Chaînage des constructeurs/destructeurs
Section intitulée « 3. Chaînage des constructeurs/destructeurs »Ordre de construction
Section intitulée « Ordre de construction »- Le constructeur de la classe de base s’exécute EN PREMIER
- Le constructeur de la classe dérivée s’exécute EN SECOND
class ClapTrap {public: ClapTrap(std::string name) { std::cout << "ClapTrap constructor" << std::endl; }};
class ScavTrap : public ClapTrap {public: ScavTrap(std::string name) : ClapTrap(name) { std::cout << "ScavTrap constructor" << std::endl; }};
// Créer un ScavTrap affiche :// ClapTrap constructor// ScavTrap constructorOrganigramme du chaînage des constructeurs
Section intitulée « Organigramme du chaînage des constructeurs »┌─────────────────────────────────────────────────────────────────┐│ Ordre d'exécution des constructeurs dans l'héritage │├─────────────────────────────────────────────────────────────────┤│ ││ Ordre de construction (Haut vers bas) : ││ ┌────────────────────────────────────────────────────────┐ ││ │ │ ││ │ ┌─────────────┐ │ ││ │ │ 1. BASE │ ◄── Constructeur ClapTrap EN PREMIER │ ││ │ │ CONSTRUCTEUR│ Initialise les membres hérités │ ││ │ └──────┬──────┘ │ ││ │ │ │ ││ │ ▼ │ ││ │ ┌─────────────┐ │ ││ │ │ 2. MEMBRES │ ◄── Constructeurs des variables │ ││ │ │ CONSTRUCTEURS│ (si des membres sont des objets) │ ││ │ └──────┬──────┘ │ ││ │ │ │ ││ │ ▼ │ ││ │ ┌─────────────┐ │ ││ │ │ 3. DÉRIVÉE │ ◄── Corps du constructeur ScavTrap │ ││ │ │ CONSTRUCTEUR│ Initialise ses propres membres │ ││ │ └──────┬──────┘ │ ││ │ │ │ ││ │ ▼ │ ││ │ [OBJET PRÊT] │ ││ │ │ ││ └────────────────────────────────────────────────────────┘ ││ ││ Ordre de destruction (Bas vers haut - INVERSE) : ││ ┌────────────────────────────────────────────────────────┐ ││ │ │ ││ │ ┌─────────────┐ │ ││ │ │ 1. DÉRIVÉE │ ◄── Destructeur ScavTrap EN PREMIER │ ││ │ │ DESTRUCTEUR │ Nettoie ses propres ressources │ ││ │ └──────┬──────┘ │ ││ │ │ │ ││ │ ▼ │ ││ │ ┌─────────────┐ │ ││ │ │ 2. MEMBRES │ ◄── Destructeurs des membres │ ││ │ │ DESTRUCTEURS│ │ ││ │ └──────┬──────┘ │ ││ │ │ │ ││ │ ▼ │ ││ │ ┌─────────────┐ │ ││ │ │ 3. BASE │ ◄── Destructeur ClapTrap EN DERNIER │ ││ │ │ DESTRUCTEUR │ Nettoie ressources héritées │ ││ │ └─────────────┘ │ ││ │ │ ││ └────────────────────────────────────────────────────────┘ ││ ││ Rappelez-vous : Construction HAUT-BAS (base → dérivée) ││ Destruction BAS-HAUT (dérivée → base) ││ │└─────────────────────────────────────────────────────────────────┘Ordre de destruction (INVERSE)
Section intitulée « Ordre de destruction (INVERSE) »- Le destructeur de la classe dérivée s’exécute EN PREMIER
- Le destructeur de la classe de base s’exécute EN SECOND
class ClapTrap {public: ~ClapTrap() { std::cout << "ClapTrap destructor" << std::endl; }};
class ScavTrap : public ClapTrap {public: ~ScavTrap() { std::cout << "ScavTrap destructor" << std::endl; }};
// Détruire un ScavTrap affiche :// ScavTrap destructor// ClapTrap destructorAppeler le constructeur de base
Section intitulée « Appeler le constructeur de base »class ScavTrap : public ClapTrap {public: // DOIT appeler le constructeur de base dans la liste d'initialisation ScavTrap(std::string name) : ClapTrap(name) { // Base est déjà construite ici _hitPoints = 100; // Redéfinir les valeurs de base _energyPoints = 50; _attackDamage = 20; }};Constructeur de copie dans les classes dérivées
Section intitulée « Constructeur de copie dans les classes dérivées »Le constructeur de copie doit explicitement appeler le constructeur de copie de la classe de base :
class ScavTrap : public ClapTrap {public: // Constructeur de copie - appeler le constructeur de copie de base ScavTrap(const ScavTrap& other) : ClapTrap(other) { // other est un ScavTrap, qui EST-UN ClapTrap // Le constructeur de copie de la classe de base gère les membres hérités std::cout << "ScavTrap copy constructor" << std::endl; }};Opérateur d’assignation dans les classes dérivées
Section intitulée « Opérateur d’assignation dans les classes dérivées »L’opérateur d’assignation doit appeler l’opérateur d’assignation de la classe de base :
class ScavTrap : public ClapTrap {public: ScavTrap& operator=(const ScavTrap& other) { if (this != &other) { // Appeler l'opérateur d'assignation de la classe de base ClapTrap::operator=(other);
// Assigner les membres de la classe dérivée (s'il y en a) // _derivedMember = other._derivedMember; } return *this; }};Forme canonique orthodoxe complète pour classe dérivée
Section intitulée « Forme canonique orthodoxe complète pour classe dérivée »class ScavTrap : public ClapTrap {public: // Constructeur par défaut ScavTrap() : ClapTrap() { _hitPoints = 100; _energyPoints = 50; _attackDamage = 20; }
// Constructeur paramétré ScavTrap(std::string name) : ClapTrap(name) { _hitPoints = 100; _energyPoints = 50; _attackDamage = 20; }
// Constructeur de copie ScavTrap(const ScavTrap& other) : ClapTrap(other) { // Membres dérivés copiés ici s'il y en a }
// Opérateur d'assignation ScavTrap& operator=(const ScavTrap& other) { if (this != &other) { ClapTrap::operator=(other); } return *this; }
// Destructeur ~ScavTrap() { // Nettoyage dérivé (destructeur de base appelé automatiquement) }};4. Redéfinition de fonctions
Section intitulée « 4. Redéfinition de fonctions »Redéfinition de base
Section intitulée « Redéfinition de base »class ClapTrap {public: void attack(const std::string& target) { std::cout << "ClapTrap " << _name << " attacks " << target << std::endl; }};
class ScavTrap : public ClapTrap {public: void attack(const std::string& target) { std::cout << "ScavTrap " << _name << " attacks " << target << std::endl; }};
ClapTrap clap("Clappy");ScavTrap scav("Scavvy");
clap.attack("enemy"); // "ClapTrap Clappy attacks enemy"scav.attack("enemy"); // "ScavTrap Scavvy attacks enemy"Appeler la fonction de la classe de base
Section intitulée « Appeler la fonction de la classe de base »class ScavTrap : public ClapTrap {public: void attack(const std::string& target) { // Appeler la version de la classe de base ClapTrap::attack(target);
// Ajouter un comportement supplémentaire std::cout << "...with extra ScavTrap power!" << std::endl; }};Masquage de nom
Section intitulée « Masquage de nom »Quand une classe dérivée déclare un membre avec le même nom qu’un membre de la classe de base, il masque (cache) le membre de la classe de base :
class Base {public: void print() { std::cout << "Base" << std::endl; } void print(int x) { std::cout << "Base: " << x << std::endl; }};
class Derived : public Base {public: void print() { std::cout << "Derived" << std::endl; } // print(int) est maintenant MASQUÉ !};
Derived d;d.print(); // OK : "Derived"d.print(42); // ERREUR : print(int) est masqué !d.Base::print(42); // OK : résolution de portée expliciteLa déclaration using
Section intitulée « La déclaration using »Amener les membres de la classe de base dans la portée de la classe dérivée :
class DiamondTrap : public ScavTrap, public FragTrap {public: // Amener attack de ScavTrap dans la portée de DiamondTrap using ScavTrap::attack;
// Maintenant DiamondTrap::attack() appelle ScavTrap::attack()};
// Aussi utile pour dé-masquer les fonctions surchargées :class Derived : public Base {public: using Base::print; // Amener TOUTES les surcharges de Base::print void print() { std::cout << "Derived" << std::endl; }};
Derived d;d.print(); // OK : "Derived"d.print(42); // OK : Base::print(int) est maintenant visible5. Membres protected
Section intitulée « 5. Membres protected »Pourquoi protected ?
Section intitulée « Pourquoi protected ? »class ClapTrap {private: std::string _name; // Seul ClapTrap peut y accéder
protected: int _hitPoints; // ClapTrap ET les classes dérivées peuvent y accéder
public: void display(); // Tout le monde peut y accéder};
class ScavTrap : public ClapTrap {public: void specialAttack() { // _name = "X"; // ERREUR : private dans ClapTrap _hitPoints -= 10; // OK : protected est accessible }};Résumé des accès
Section intitulée « Résumé des accès »class Base {private: int _private; // Seulement les méthodes de Baseprotected: int _protected; // Méthodes de Base + Derivedpublic: int _public; // Tout le monde};6. Structure de l’exercice ClapTrap
Section intitulée « 6. Structure de l’exercice ClapTrap »ex00 : ClapTrap (Classe de base)
Section intitulée « ex00 : ClapTrap (Classe de base) »class ClapTrap {protected: std::string _name; int _hitPoints; // 10 int _energyPoints; // 10 int _attackDamage; // 0
public: ClapTrap(std::string name); ClapTrap(const ClapTrap& other); ClapTrap& operator=(const ClapTrap& other); ~ClapTrap();
void attack(const std::string& target); void takeDamage(unsigned int amount); void beRepaired(unsigned int amount);};ex01 : ScavTrap (Hérite de ClapTrap)
Section intitulée « ex01 : ScavTrap (Hérite de ClapTrap) »class ScavTrap : public ClapTrap {public: ScavTrap(std::string name); ScavTrap(const ScavTrap& other); ScavTrap& operator=(const ScavTrap& other); ~ScavTrap();
void attack(const std::string& target); // Override void guardGate(); // Nouvelle capacité};
// Le constructeur doit initialiser la base avec des valeurs différentes :// _hitPoints = 100, _energyPoints = 50, _attackDamage = 20ex02 : FragTrap (Hérite de ClapTrap)
Section intitulée « ex02 : FragTrap (Hérite de ClapTrap) »class FragTrap : public ClapTrap {public: FragTrap(std::string name); FragTrap(const FragTrap& other); FragTrap& operator=(const FragTrap& other); ~FragTrap();
void highFivesGuys(); // Nouvelle capacité};
// _hitPoints = 100, _energyPoints = 100, _attackDamage = 307. Héritage multiple et le problème du diamant (ex03)
Section intitulée « 7. Héritage multiple et le problème du diamant (ex03) »Le problème du diamant
Section intitulée « Le problème du diamant » ClapTrap / \ ScavTrap FragTrap \ / DiamondTrapSans héritage virtuel :
- DiamondTrap a DEUX copies de ClapTrap
- Ambiguïté : quel _name de ClapTrap ?
Solution avec héritage virtuel
Section intitulée « Solution avec héritage virtuel »class ClapTrap { // ...};
class ScavTrap : virtual public ClapTrap { // ^^^^^^^ MOT CLÉ};
class FragTrap : virtual public ClapTrap { // ^^^^^^^ MOT CLÉ};
class DiamondTrap : public ScavTrap, public FragTrap { // Maintenant seulement UN sous-objet ClapTrap};Implémentation de DiamondTrap
Section intitulée « Implémentation de DiamondTrap »class DiamondTrap : public ScavTrap, public FragTrap {private: std::string _name; // Même nom de variable que ClapTrap::_name
public: DiamondTrap(std::string name); ~DiamondTrap();
// Utilise attack de ScavTrap using ScavTrap::attack;
void whoAmI();};
DiamondTrap::DiamondTrap(std::string name) : ClapTrap(name + "_clap_name"), // Initialiser la base virtuelle ScavTrap(name), FragTrap(name), _name(name){ // Attributs de FragTrap sauf energy de ScavTrap _hitPoints = FragTrap::_hitPoints; // ou juste 100 _energyPoints = ScavTrap::_energyPoints; // ou juste 50 _attackDamage = FragTrap::_attackDamage; // ou juste 30}
void DiamondTrap::whoAmI() { std::cout << "I am " << _name << std::endl; std::cout << "My ClapTrap name is " << ClapTrap::_name << std::endl;}Construction de la classe de base virtuelle
Section intitulée « Construction de la classe de base virtuelle »Avec l’héritage virtuel, la classe LA PLUS DÉRIVÉE doit initialiser la base virtuelle :
DiamondTrap::DiamondTrap(std::string name) : ClapTrap(name + "_clap_name"), // DiamondTrap initialise ClapTrap ScavTrap(name), // L'init ClapTrap de ScavTrap est ignorée FragTrap(name), // L'init ClapTrap de FragTrap est ignorée _name(name){}8. Bonnes pratiques
Section intitulée « 8. Bonnes pratiques »Quand utiliser l’héritage
Section intitulée « Quand utiliser l’héritage »- Relation « est-un » : ScavTrap EST UN ClapTrap
- Réutilisation du code : Les classes dérivées partagent le code de la classe de base
- Polymorphisme : Traiter la dérivée comme la base (Module 04)
Quand NE PAS utiliser l’héritage
Section intitulée « Quand NE PAS utiliser l’héritage »- Relation « a-un » : Utilisez la composition à la place
- Juste pour la réutilisation de code avec des classes non liées
- Quand la relation n’a pas de sens sémantique
Erreurs courantes
Section intitulée « Erreurs courantes »- Oublier d’appeler le constructeur de base
- Mauvaises attentes sur l’ordre de destruction
- Accéder aux membres private (pas protected) de la base
- Ne pas utiliser l’héritage virtuel pour le diamant
Exercice 00 : ClapTrap
Section intitulée « Exercice 00 : ClapTrap »Analyse du sujet
Section intitulée « Analyse du sujet »Créez une classe de base ClapTrap - un petit robot qui peut :
- attack : Inflige des dégâts, coûte 1 point d’énergie
- takeDamage : Réduit les points de vie
- beRepaired : Restaure les points de vie, coûte 1 point d’énergie
Attributs de départ :
| Attribut | Valeur |
|---|---|
| Name | (paramètre du constructeur) |
| Hit points | 10 |
| Energy points | 10 |
| Attack damage | 0 |
Stratégie d’approche
Section intitulée « Stratégie d’approche »Étape 1 - Planifier pour l’héritage
Même si cet exercice ne nécessite pas l’héritage, les suivants si. Utilisez protected pour les attributs afin que les classes dérivées puissent y accéder.
Étape 2 - Implémenter la vérification des ressources
Les actions nécessitent de l’énergie et des HP. Si l’un des deux est à zéro, l’action échoue.
Étape 3 - Suivre l’OCF
Incluez les quatre membres de la forme canonique orthodoxe avec des messages de constructeur/destructeur.
Construction progressive du code
Section intitulée « Construction progressive du code »Étape 1 - Déclaration de classe :
#ifndef CLAPTRAP_HPP#define CLAPTRAP_HPP
#include <string>
class ClapTrap {protected: // Protected, pas private - les classes dérivées ont besoin d'accès ! std::string _name; int _hitPoints; int _energyPoints; int _attackDamage;
public: ClapTrap(std::string name); ClapTrap(const ClapTrap& other); ClapTrap& operator=(const ClapTrap& other); ~ClapTrap();
void attack(const std::string& target); void takeDamage(unsigned int amount); void beRepaired(unsigned int amount);};
#endifÉtape 2 - Constructeur avec messages :
#include "ClapTrap.hpp"#include <iostream>
ClapTrap::ClapTrap(std::string name) : _name(name), _hitPoints(10), _energyPoints(10), _attackDamage(0){ std::cout << "ClapTrap " << _name << " constructed" << std::endl;}
ClapTrap::~ClapTrap() { std::cout << "ClapTrap " << _name << " destructed" << std::endl;}Étape 3 - Actions avec vérification des ressources :
void ClapTrap::attack(const std::string& target) { if (_energyPoints <= 0 || _hitPoints <= 0) { std::cout << "ClapTrap " << _name << " can't attack (no energy/HP)!" << std::endl; return; } _energyPoints--; std::cout << "ClapTrap " << _name << " attacks " << target << ", causing " << _attackDamage << " points of damage!" << std::endl;}
void ClapTrap::takeDamage(unsigned int amount) { _hitPoints -= amount; if (_hitPoints < 0) _hitPoints = 0; std::cout << "ClapTrap " << _name << " takes " << amount << " damage! HP: " << _hitPoints << std::endl;}
void ClapTrap::beRepaired(unsigned int amount) { if (_energyPoints <= 0 || _hitPoints <= 0) { std::cout << "ClapTrap " << _name << " can't repair (no energy/HP)!" << std::endl; return; } _energyPoints--; _hitPoints += amount; std::cout << "ClapTrap " << _name << " repairs " << amount << " HP! HP: " << _hitPoints << std::endl;}Explication ligne par ligne
Section intitulée « Explication ligne par ligne »| Ligne | Code | Pourquoi |
|---|---|---|
protected: | Spécificateur d’accès | Les classes dérivées peuvent accéder, pas les autres |
if (_energyPoints <= 0) | Vérification ressources | Ne peut pas agir sans énergie |
_energyPoints-- | Consommer ressource | Les actions coûtent de l’énergie |
unsigned int amount | Paramètre non signé | Les dégâts ne peuvent pas être négatifs |
Pièges courants
Section intitulée « Pièges courants »1. Utiliser private au lieu de protected
// FAUX - les classes dérivées ne peuvent pas accéder !private: std::string _name;
// CORRECT - les classes dérivées peuvent accéderprotected: std::string _name;2. Oublier les vérifications de ressources
// FAUX - peut attaquer sans énergie !void ClapTrap::attack(const std::string& target) { _energyPoints--; // Devient négatif ! // ...}
// CORRECT - vérifier d'abordvoid ClapTrap::attack(const std::string& target) { if (_energyPoints <= 0 || _hitPoints <= 0) { // Gérer l'échec return; } _energyPoints--; // ...}Conseils de test
Section intitulée « Conseils de test »int main() { ClapTrap a("Bob");
// Tester les actions de base a.attack("Enemy"); a.takeDamage(5); a.beRepaired(3);
// Tester l'épuisement des ressources for (int i = 0; i < 12; i++) // Devrait échouer après 10 a.attack("Enemy");
return 0;}Code final
Section intitulée « Code final »#include "ClapTrap.hpp"#include <iostream>
ClapTrap::ClapTrap(std::string name) : _name(name), _hitPoints(10), _energyPoints(10), _attackDamage(0){ std::cout << "ClapTrap " << _name << " constructed" << std::endl;}
ClapTrap::ClapTrap(const ClapTrap& other) : _name(other._name), _hitPoints(other._hitPoints), _energyPoints(other._energyPoints), _attackDamage(other._attackDamage){ std::cout << "ClapTrap " << _name << " copy constructed" << std::endl;}
ClapTrap& ClapTrap::operator=(const ClapTrap& other) { std::cout << "ClapTrap assignment operator called" << std::endl; if (this != &other) { _name = other._name; _hitPoints = other._hitPoints; _energyPoints = other._energyPoints; _attackDamage = other._attackDamage; } return *this;}
ClapTrap::~ClapTrap() { std::cout << "ClapTrap " << _name << " destructed" << std::endl;}
void ClapTrap::attack(const std::string& target) { if (_energyPoints <= 0 || _hitPoints <= 0) { std::cout << "ClapTrap " << _name << " can't attack!" << std::endl; return; } _energyPoints--; std::cout << "ClapTrap " << _name << " attacks " << target << ", causing " << _attackDamage << " damage!" << std::endl;}
void ClapTrap::takeDamage(unsigned int amount) { _hitPoints -= amount; if (_hitPoints < 0) _hitPoints = 0; std::cout << "ClapTrap " << _name << " takes " << amount << " damage!" << std::endl;}
void ClapTrap::beRepaired(unsigned int amount) { if (_energyPoints <= 0 || _hitPoints <= 0) { std::cout << "ClapTrap " << _name << " can't repair!" << std::endl; return; } _energyPoints--; _hitPoints += amount; std::cout << "ClapTrap " << _name << " repairs " << amount << " HP!" << std::endl;}Exercice 01 : ScavTrap
Section intitulée « Exercice 01 : ScavTrap »Analyse du sujet
Section intitulée « Analyse du sujet »Créez ScavTrap qui hérite de ClapTrap avec :
-
Valeurs d’attributs différentes :
Attribut Valeur Hit points 100 Energy points 50 Attack damage 20 -
Message d’attaque personnalisé
-
Nouvelle capacité :
guardGate()
Stratégie d’approche
Section intitulée « Stratégie d’approche »Étape 1 - Hériter de ClapTrap
Utilisez : public ClapTrap pour établir l’héritage.
Étape 2 - Chaînage des constructeurs
Appelez d’abord le constructeur de base, PUIS redéfinissez les valeurs.
Étape 3 - Comprendre l’ordre de construction
Lors de la création d’un ScavTrap :
- Le constructeur de ClapTrap s’exécute en premier
- Puis le constructeur de ScavTrap s’exécute
La destruction est inverse :
- Le destructeur de ScavTrap s’exécute en premier
- Puis le destructeur de ClapTrap s’exécute
Construction progressive du code
Section intitulée « Construction progressive du code »Étape 1 - Déclaration de classe :
#ifndef SCAVTRAP_HPP#define SCAVTRAP_HPP
#include "ClapTrap.hpp"
class ScavTrap : public ClapTrap { // Hérite de ClapTrappublic: ScavTrap(std::string name); ScavTrap(const ScavTrap& other); ScavTrap& operator=(const ScavTrap& other); ~ScavTrap();
void attack(const std::string& target); // Override l'attack de ClapTrap void guardGate(); // Nouvelle capacité};
#endifÉtape 2 - Constructeur avec initialisation de base :
#include "ScavTrap.hpp"#include <iostream>
ScavTrap::ScavTrap(std::string name) : ClapTrap(name) { // Le constructeur de ClapTrap s'est déjà exécuté avec les valeurs par défaut // Maintenant nous redéfinissons avec les valeurs de ScavTrap _hitPoints = 100; _energyPoints = 50; _attackDamage = 20; std::cout << "ScavTrap " << _name << " constructed" << std::endl;}
ScavTrap::~ScavTrap() { std::cout << "ScavTrap " << _name << " destructed" << std::endl;}Étape 3 - Override attack et ajouter nouvelle capacité :
void ScavTrap::attack(const std::string& target) { if (_energyPoints <= 0 || _hitPoints <= 0) { std::cout << "ScavTrap " << _name << " can't attack!" << std::endl; return; } _energyPoints--; std::cout << "ScavTrap " << _name << " attacks " << target << ", causing " << _attackDamage << " damage!" << std::endl;}
void ScavTrap::guardGate() { std::cout << "ScavTrap " << _name << " is now in Gate keeper mode" << std::endl;}Explication ligne par ligne
Section intitulée « Explication ligne par ligne »| Ligne | Code | Pourquoi |
|---|---|---|
: public ClapTrap | Héritage public | ScavTrap EST-UN ClapTrap |
: ClapTrap(name) | Appel constructeur de base | Initialiser la partie ClapTrap d’abord |
_hitPoints = 100 | Redéfinir valeur | ScavTrap a des stats différentes |
void attack(...) | Override fonction | ScavTrap a un message d’attaque personnalisé |
Pièges courants
Section intitulée « Pièges courants »1. Ne pas appeler le constructeur de base
// FAUX - ClapTrap pas correctement initialisé !ScavTrap::ScavTrap(std::string name) { _name = name; _hitPoints = 100; // ...}
// CORRECT - appeler le constructeur de base d'abordScavTrap::ScavTrap(std::string name) : ClapTrap(name) { _hitPoints = 100; // ...}2. Définir les valeurs dans la liste d’initialisation (ne redéfinira pas)
// FAUX - le constructeur de base les a déjà définis !ScavTrap::ScavTrap(std::string name) : ClapTrap(name), _hitPoints(100) { // Erreur : ne peut pas init dans dérivée}
// CORRECT - redéfinir dans le corps du constructeurScavTrap::ScavTrap(std::string name) : ClapTrap(name) { _hitPoints = 100; // Redéfinir après construction de base}Conseils de test
Section intitulée « Conseils de test »int main() { std::cout << "=== Creating ScavTrap ===" << std::endl; ScavTrap s("Scav");
s.attack("Target"); // Devrait montrer le message ScavTrap s.guardGate(); // Capacité exclusive à ScavTrap
std::cout << "=== End of main ===" << std::endl; return 0;}// Sortie attendue :// ClapTrap Scav constructed// ScavTrap Scav constructed// ScavTrap Scav attacks Target, causing 20 damage!// ScavTrap Scav is now in Gate keeper mode// ScavTrap Scav destructed// ClapTrap Scav destructedCode final
Section intitulée « Code final »#include "ScavTrap.hpp"#include <iostream>
ScavTrap::ScavTrap(std::string name) : ClapTrap(name) { _hitPoints = 100; _energyPoints = 50; _attackDamage = 20; std::cout << "ScavTrap " << _name << " constructed" << std::endl;}
ScavTrap::ScavTrap(const ScavTrap& other) : ClapTrap(other) { std::cout << "ScavTrap " << _name << " copy constructed" << std::endl;}
ScavTrap& ScavTrap::operator=(const ScavTrap& other) { ClapTrap::operator=(other); return *this;}
ScavTrap::~ScavTrap() { std::cout << "ScavTrap " << _name << " destructed" << std::endl;}
void ScavTrap::attack(const std::string& target) { if (_energyPoints <= 0 || _hitPoints <= 0) { std::cout << "ScavTrap " << _name << " can't attack!" << std::endl; return; } _energyPoints--; std::cout << "ScavTrap " << _name << " attacks " << target << ", causing " << _attackDamage << " damage!" << std::endl;}
void ScavTrap::guardGate() { std::cout << "ScavTrap " << _name << " is now in Gate keeper mode" << std::endl;}Exercice 02 : FragTrap
Section intitulée « Exercice 02 : FragTrap »Analyse du sujet
Section intitulée « Analyse du sujet »Créez FragTrap qui hérite de ClapTrap avec :
| Attribut | Valeur |
|---|---|
| Hit points | 100 |
| Energy points | 100 |
| Attack damage | 30 |
Nouvelle capacité : highFivesGuys() - demande un high five.
Stratégie d’approche
Section intitulée « Stratégie d’approche »Même schéma que ScavTrap - cet exercice renforce les concepts d’héritage.
Construction progressive du code
Section intitulée « Construction progressive du code »#ifndef FRAGTRAP_HPP#define FRAGTRAP_HPP
#include "ClapTrap.hpp"
class FragTrap : public ClapTrap {public: FragTrap(std::string name); FragTrap(const FragTrap& other); FragTrap& operator=(const FragTrap& other); ~FragTrap();
void highFivesGuys();};
#endif#include "FragTrap.hpp"#include <iostream>
FragTrap::FragTrap(std::string name) : ClapTrap(name) { _hitPoints = 100; _energyPoints = 100; _attackDamage = 30; std::cout << "FragTrap " << _name << " constructed" << std::endl;}
FragTrap::FragTrap(const FragTrap& other) : ClapTrap(other) { std::cout << "FragTrap " << _name << " copy constructed" << std::endl;}
FragTrap& FragTrap::operator=(const FragTrap& other) { ClapTrap::operator=(other); return *this;}
FragTrap::~FragTrap() { std::cout << "FragTrap " << _name << " destructed" << std::endl;}
void FragTrap::highFivesGuys() { std::cout << "FragTrap " << _name << " requests a high five!" << std::endl;}Pièges courants
Section intitulée « Pièges courants »Identiques à ScavTrap - assurez-vous d’appeler le constructeur de base !
Conseils de test
Section intitulée « Conseils de test »int main() { FragTrap f("Frag"); f.attack("Enemy"); // Utilise ClapTrap::attack (pas redéfini) f.highFivesGuys(); // Capacité exclusive à FragTrap return 0;}Exercice 03 : DiamondTrap
Section intitulée « Exercice 03 : DiamondTrap »Analyse du sujet
Section intitulée « Analyse du sujet »Créez DiamondTrap qui hérite À LA FOIS de ScavTrap ET FragTrap :
ClapTrap / \ ScavTrap FragTrap \ / DiamondTrapCela crée le problème du diamant : sans gestion spéciale, DiamondTrap aurait DEUX copies de ClapTrap !
Exigences :
- Name : Propre nom de DiamondTrap, nom ClapTrap = name + “_clap_name”
- HP : Ceux de FragTrap (100)
- Energy : Ceux de ScavTrap (50)
- Damage : Ceux de FragTrap (30)
- attack() : Celui de ScavTrap
whoAmI(): Affiche les deux noms
Stratégie d’approche
Section intitulée « Stratégie d’approche »Étape 1 - Comprendre le problème du diamant
Sans virtual :
DiamondTrap a :- ScavTrap::ClapTrap (une copie)- FragTrap::ClapTrap (une autre copie !)Avec virtual :
DiamondTrap a :- UN ClapTrap partagé (la bonne façon)Étape 2 - Ajouter virtual aux classes intermédiaires
ScavTrap et FragTrap doivent tous deux utiliser virtual public ClapTrap.
Étape 3 - Initialiser la base virtuelle dans la classe la plus dérivée
Avec l’héritage virtuel, la classe LA PLUS DÉRIVÉE (DiamondTrap) initialise la base virtuelle (ClapTrap).
Construction progressive du code
Section intitulée « Construction progressive du code »Étape 1 - Modifier ScavTrap et FragTrap (ajouter virtual) :
class ScavTrap : virtual public ClapTrap { // Ajouter virtual // ... reste inchangé};class FragTrap : virtual public ClapTrap { // Ajouter virtual // ... reste inchangé};Étape 2 - Déclaration de DiamondTrap :
#ifndef DIAMONDTRAP_HPP#define DIAMONDTRAP_HPP
#include "ScavTrap.hpp"#include "FragTrap.hpp"
class DiamondTrap : public ScavTrap, public FragTrap {private: std::string _name; // Propre nom de DiamondTrap (masque ClapTrap::_name)
public: DiamondTrap(std::string name); DiamondTrap(const DiamondTrap& other); DiamondTrap& operator=(const DiamondTrap& other); ~DiamondTrap();
using ScavTrap::attack; // Utiliser attack de ScavTrap void whoAmI();};
#endifÉtape 3 - Implémentation de DiamondTrap :
#include "DiamondTrap.hpp"#include <iostream>
DiamondTrap::DiamondTrap(std::string name) : ClapTrap(name + "_clap_name"), // Initialiser la base virtuelle EN PREMIER ScavTrap(name), FragTrap(name), _name(name){ // Prendre des attributs spécifiques de chaque parent _hitPoints = FragTrap::_hitPoints; // 100 de FragTrap _energyPoints = ScavTrap::_energyPoints; // 50 de ScavTrap _attackDamage = FragTrap::_attackDamage; // 30 de FragTrap std::cout << "DiamondTrap " << _name << " constructed" << std::endl;}
DiamondTrap::~DiamondTrap() { std::cout << "DiamondTrap " << _name << " destructed" << std::endl;}
void DiamondTrap::whoAmI() { std::cout << "I am " << _name << std::endl; std::cout << "My ClapTrap name is " << ClapTrap::_name << std::endl;}Explication ligne par ligne
Section intitulée « Explication ligne par ligne »| Ligne | Code | Pourquoi |
|---|---|---|
virtual public ClapTrap | Héritage virtuel | Empêche le ClapTrap dupliqué |
: ClapTrap(name + "_clap_name") | Init base virtuelle | La classe la plus dérivée initialise la base virtuelle |
std::string _name | Propre membre | Masque ClapTrap::_name |
using ScavTrap::attack | Résoudre ambiguïté | Choisir explicitement attack de ScavTrap |
ClapTrap::_name | Résolution de portée | Accéder au membre de la classe de base |
Pièges courants
Section intitulée « Pièges courants »1. Oublier virtual sur les classes intermédiaires
// FAUX - deux copies de ClapTrap !class ScavTrap : public ClapTrap { };class FragTrap : public ClapTrap { };
// CORRECT - un ClapTrap partagéclass ScavTrap : virtual public ClapTrap { };class FragTrap : virtual public ClapTrap { };2. Ne pas initialiser la base virtuelle dans DiamondTrap
// FAUX - ClapTrap pas correctement initialisé !DiamondTrap::DiamondTrap(std::string name) : ScavTrap(name), FragTrap(name) { }
// CORRECT - doit initialiser ClapTrap explicitementDiamondTrap::DiamondTrap(std::string name) : ClapTrap(name + "_clap_name"), ScavTrap(name), FragTrap(name) { }3. Confondre les deux membres _name
void DiamondTrap::whoAmI() { // _name est le propre membre de DiamondTrap // ClapTrap::_name est le membre de la classe de base std::cout << _name << std::endl; // Nom DiamondTrap std::cout << ClapTrap::_name << std::endl; // Nom ClapTrap (avec suffixe)}Conseils de test
Section intitulée « Conseils de test »int main() { std::cout << "=== Creating DiamondTrap ===" << std::endl; DiamondTrap d("Diamond");
d.whoAmI(); // Montre les deux noms d.attack("Enemy"); // Utilise attack de ScavTrap d.guardGate(); // Hérité de ScavTrap d.highFivesGuys(); // Hérité de FragTrap
std::cout << "=== End of main ===" << std::endl; return 0;}// Ordre de construction attendu :// ClapTrap Diamond_clap_name constructed// ScavTrap Diamond constructed// FragTrap Diamond constructed// DiamondTrap Diamond constructed//// Ordre de destruction attendu (inverse) :// DiamondTrap Diamond destructed// FragTrap Diamond destructed// ScavTrap Diamond destructed// ClapTrap Diamond_clap_name destructedCode final
Section intitulée « Code final »#include "DiamondTrap.hpp"#include <iostream>
DiamondTrap::DiamondTrap(std::string name) : ClapTrap(name + "_clap_name"), ScavTrap(name), FragTrap(name), _name(name){ _hitPoints = FragTrap::_hitPoints; _energyPoints = ScavTrap::_energyPoints; _attackDamage = FragTrap::_attackDamage; std::cout << "DiamondTrap " << _name << " constructed" << std::endl;}
DiamondTrap::DiamondTrap(const DiamondTrap& other) : ClapTrap(other), ScavTrap(other), FragTrap(other), _name(other._name){ std::cout << "DiamondTrap " << _name << " copy constructed" << std::endl;}
DiamondTrap& DiamondTrap::operator=(const DiamondTrap& other) { ClapTrap::operator=(other); _name = other._name; return *this;}
DiamondTrap::~DiamondTrap() { std::cout << "DiamondTrap " << _name << " destructed" << std::endl;}
void DiamondTrap::whoAmI() { std::cout << "I am " << _name << std::endl; std::cout << "My ClapTrap name is " << ClapTrap::_name << std::endl;}Référence rapide
Section intitulée « Référence rapide »// Héritage de baseclass Derived : public Base { };
// Chaînage des constructeursDerived(args) : Base(base_args), _member(val) { }
// Override fonctionvoid Derived::method() { Base::method(); /* extra */ }
// Héritage virtuel (diamant)class Middle : virtual public Base { };Concepts connexes
Section intitulée « Concepts connexes »Continuez votre parcours C++ :
- Précédent : Module 02 : OCF & Opérateurs - Réviser la sémantique de copie
- Suivant : Module 04 : Polymorphisme - Apprendre comment les fonctions virtuelles permettent la sélection de comportement à l’exécution
Termes clés de ce module
Section intitulée « Termes clés de ce module »Visitez le Glossaire pour les définitions de :