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 survient 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 provoque 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 via Mammal et l'autre via 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 base commune existe. Ajoutez le mot-clé virtual lors de l'héritage de la 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 ! Une seule instance de 'age' existe

Notez 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, indépendamment des classes intermédiaires.

challenge icon

Défi

Facile

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

Vous organiserez votre code sur quatre fichiers :

  • Employee.h : Définissez 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éfinissez une classe Developer qui utilise l'héritage public virtuel de Employee :
    • Un membre protégé std::string language
    • Un constructeur qui prend le nom, l'id et le langage — transmet le nom et l'id à Employee, stocke 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éfinissez une classe Manager qui utilise l'héritage public virtuel de Employee :
    • Un membre protégé int teamSize
    • Un constructeur qui prend le nom, l'id et la taille de l'équipe — transmet le nom et l'id à Employee, stocke 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 : Lisez quatre entrées (chacune sur une ligne séparée) :
    1. Nom (chaîne de caractères)
    2. ID de l'employé (entier)
    3. Langage de programmation (chaîne de caractères)
    4. Taille de l'équipe (entier)

    Définissez une classe TeamLead qui hérite publiquement 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éez un objet TeamLead à l'intérieur d'une portée de bloc, appelez showRole(), puis laissez-le sortir de la portée. Après le bloc, affichez : 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. Le 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.

Aide-mémoire

Le problème du diamant survient lorsqu'une classe hérite de deux classes qui partagent une classe de base commune, ce qui entraîne la présence de deux copies de la classe de base et provoque une ambiguïté.

class Animal {
public:
    int age;
};

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

class Bat : public Mammal, public Bird {};

Bat b;
b.age = 5;  // Error: ambiguous - which 'age'?

L'héritage virtuel résout le problème du diamant en garantissant qu'une seule copie de la classe de base commune existe. Utilisez le mot-clé virtual lors de l'héritage :

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;  // Works! Only one 'age' exists

Avec l'héritage virtuel, la classe la plus dérivée doit initialiser directement la classe de base virtuelle dans son constructeur, indépendamment des classes intermédiaires.

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 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, ensuite Manager
    // Devrait afficher : 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()
    // Devrait appeler getInfo(), code(), et manage() dans cet ordre
    void showRole() {
        // TODO: Appeler les trois méthodes
    }

    // TODO: Implémenter le destructeur
    // Devrait afficher : 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'une portée de bloc
    // Appeler showRole(), puis le laisser 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