Menu
Coddy logo textTech

Héritage virtuel

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

Le problème du diamant se produit lorsqu’une classe hérite de deux classes qui partagent une classe de base commune. Sans traitement particulier, la classe dérivée se retrouve avec deux copies de la base commune, ce qui entraîne une ambiguïté et un gaspillage de mémoire.

class Animal {
public:
    int age;
};

class Mammal : public Animal {};
class Bird : public Animal {};

class Bat : public Mammal, public Bird {};

Bat b;
b.age = 5;  // Erreur : ambiguë - quel 'age' ?

La classe Bat contient deux sous-objets Animal distincts : l’un par l’intermédiaire de Mammal et l’autre par l’intermédiaire de Bird. Cela crée le diagramme d’héritage en forme de losange qui donne son nom au problème.

L’héritage virtuel résout ce problème en garantissant qu’une seule copie de la classe de base commune existe. Ajoutez le mot-clé virtual lors de l’héritage de la classe de base partagée :

class Animal {
public:
    int age;
    Animal(int a = 0) : age(a) {}
};

class Mammal : virtual public Animal {
public:
    Mammal(int a = 0) : Animal(a) {}
};

class Bird : virtual public Animal {
public:
    Bird(int a = 0) : Animal(a) {}
};

class Bat : public Mammal, public Bird {
public:
    Bat(int a) : Animal(a), Mammal(a), Bird(a) {}
};

Bat b(5);
b.age = 10;  // Fonctionne ! Il n'existe qu'un seul 'age'

Remarquez que Bat doit initialiser directement Animal dans son constructeur. Avec l’héritage virtuel, la classe la plus dérivée est responsable de la construction de la base virtuelle, quelles que soient les classes intermédiaires.

challenge icon

Défi

Facile

Construisons un système de gestion des employés qui montre comment l’héritage virtuel résout le problème du diamant. Vous allez créer une hiérarchie dans laquelle un TeamLead hérite à la fois de Developer et de Manager, qui partagent tous deux une classe de base commune Employee.

Vous allez organiser votre code dans quatre fichiers :

  • Employee.h : Définir la classe de base commune Employee avec :
    • Un std::string name et un int id protégés
    • Un constructeur qui prend les deux valeurs et affiche : Employee [<name>] hired with ID <id>
    • Une méthode publique getInfo() qui affiche : Employee: <name> (ID: <id>)
    • Un destructeur virtuel qui affiche : Employee [<name>] record closed
  • Developer.h : Définir une classe Developer qui utilise l’héritage public virtuel de Employee :
    • Un membre std::string language protégé
    • Un constructeur qui prend le nom, l’identifiant et le langage : il transmet le nom et l’identifiant à Employee, enregistre le langage et affiche : Developer [<name>] specializes in <language>
    • Une méthode publique code() qui affiche : <name> is coding in <language>
    • Un destructeur qui affiche : Developer [<name>] signed off
  • Manager.h : Définir une classe Manager qui utilise l’héritage public virtuel de Employee :
    • Un membre int teamSize protégé
    • Un constructeur qui prend le nom, l’identifiant et la taille de l’équipe : il transmet le nom et l’identifiant à Employee, enregistre la taille de l’équipe et affiche : Manager [<name>] leads a team of <teamSize>
    • Une méthode publique manage() qui affiche : <name> is managing <teamSize> people
    • Un destructeur qui affiche : Manager [<name>] stepped down
  • main.cpp : Lire quatre entrées, chacune sur une ligne distincte :
    1. Nom (chaîne de caractères)
    2. Identifiant de l’employé (entier)
    3. Langage de programmation (chaîne de caractères)
    4. Taille de l’équipe (entier)

    Définir une classe TeamLead qui hérite publiquement à la fois de Developer et de Manager :

    • Un constructeur qui prend les quatre paramètres et doit initialiser directement Employee (la base virtuelle), puis Developer et Manager
    • Le constructeur doit afficher : TeamLead [<name>] ready to lead and code!
    • Une méthode showRole() qui appelle getInfo(), code() et manage() dans cet ordre
    • Un destructeur qui affiche : TeamLead [<name>] promoted out

    Créer un objet TeamLead dans un bloc, appeler showRole(), puis le laisser sortir de sa portée. Après le bloc, afficher : Organization restructured!

Par exemple, avec les entrées Alice, 101, C++ et 5 :

Employee [Alice] hired with ID 101
Developer [Alice] specializes in C++
Manager [Alice] leads a team of 5
TeamLead [Alice] ready to lead and code!
Employee: Alice (ID: 101)
Alice is coding in C++
Alice is managing 5 people
TeamLead [Alice] promoted out
Manager [Alice] stepped down
Developer [Alice] signed off
Employee [Alice] record closed
Organization restructured!

Remarquez qu’il n’y a qu’un seul appel au constructeur de Employee et un seul appel au destructeur de Employee : l’héritage virtuel garantit qu’une seule copie de la base partagée existe. TeamLead doit initialiser directement Employee car, avec l’héritage virtuel, la classe la plus dérivée est responsable de la construction de la base virtuelle.

Essayez vous-même

#include <iostream>
#include <string>
#include "Developer.h"
#include "Manager.h"

using namespace std;

// TODO: Définir la classe TeamLead qui hérite publiquement de Developer et de Manager
// Rappel : Avec l'héritage virtuel, TeamLead doit initialiser directement Employee (la base virtuelle)
class TeamLead : public Developer, public Manager {
public:
    // TODO: Implémenter le constructeur qui prend name, id, language et teamSize
    // Doit initialiser : Employee en premier (base virtuelle), puis Developer, puis Manager
    // Should print: TeamLead [<name>] ready to lead and code!
    TeamLead(const std::string& name, int id, const std::string& language, int teamSize)
        : Employee(name, id),
          Developer(name, id, language),
          Manager(name, id, teamSize) {
        // TODO: Afficher le message du constructeur
    }

    // TODO: Implémenter la méthode showRole()
    // Doit appeler getInfo(), code() et manage() dans cet ordre
    void showRole() {
        // TODO: Appeler les trois méthodes
    }

    // TODO: Implémenter le destructeur
    // Should print: TeamLead [<name>] promoted out
    ~TeamLead() {
        // TODO: Afficher le message du destructeur
    }
};

int main() {
    // Lire les entrées
    string name;
    int id;
    string language;
    int teamSize;

    getline(cin, name);
    cin >> id;
    cin.ignore();
    getline(cin, language);
    cin >> teamSize;

    // TODO: Créer un objet TeamLead à l'intérieur d'un bloc de portée
    // Appelez showRole(), puis laissez-le sortir de la portée
    {
        // TODO: Créer TeamLead et appeler showRole()
    }

    // Afficher le message final après le bloc
    cout << "Organization restructured!" << endl;

    return 0;
}
quiz iconTestez-vous

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

Toutes les leçons de Programmation Orientée Objet

Entraînez-vous par vous-même : Compilateur C++ en ligne