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' existeNotez 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.
Défi
FacileConstruisons 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 communeEmployeeavec :- Un
std::string nameet unint idproté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
- Un
Developer.h: Définissez une classeDeveloperqui utilise l'héritage public virtuel deEmployee:- 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
- Un membre protégé
Manager.h: Définissez une classeManagerqui utilise l'héritage public virtuel deEmployee:- 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
- Un membre protégé
main.cpp: Lisez quatre entrées (chacune sur une ligne séparée) :- Nom (chaîne de caractères)
- ID de l'employé (entier)
- Langage de programmation (chaîne de caractères)
- Taille de l'équipe (entier)
Définissez une classe
TeamLeadqui hérite publiquement deDeveloperet deManager:- Un constructeur qui prend les quatre paramètres et doit initialiser directement
Employee(la base virtuelle), puisDeveloperetManager - Le constructeur doit afficher :
TeamLead [<name>] ready to lead and code! - Une méthode
showRole()qui appellegetInfo(),code()etmanage()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, appelezshowRole(), 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' existsAvec 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;
}
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
1Fondamentaux de la POO
Fichiers externesBuild et Compilation C++Fichiers d'en-tête et Fichiers sourceEspaces de noms et PortéeIntroduction à la POO en C++Classes vs ObjetsLe pointeur 'this'Méthodes (Fonctions membres)Attributs (Membres de données)Bases des Ctors et DtorsRécapitulatif - Calculatrice simple4Propriétés de classe
Membres d'instance vs membres statiquesGetters et SettersFonctions membres constMot-clé mutableMéthodes et variables statiquesFonctions et classes amiesRécapitulatif - Gestionnaire de compte bancaire7Héritage
Héritage de baseNiveaux d'accès à l'héritageOrdre d'appel des Ctor et DtorRedéfinition de méthodesFonctions virtuelles et VTableHéritage multipleHéritage virtuelRécapitulatif - Hiérarchie des employés10Présentation de la STL
Présentation et philosophie de la STLConteneurs de la STLItérateursAlgorithmes de la STLFoncteurs et expressions lambdaRécapitulatif - Fréquence des mots13Patrons de conception, Partie 1
Introduction aux patrons de conceptionPatron SingletonFabrique & Fabrique abstraitePatron MonteurPatron ObservateurPatron Stratégie2Gestion de la mémoire
Mémoire Pile vs TasPointeurs et RéférencesMémoire dynamique (new/delete)Smart Pointers en C++RAII en C++Récapitulatif - Gestionnaire de tableaux dynamiques5Encapsulation
Spécificateurs d'accès en C++Spécificateurs d'accès en profondeurMasquage d'informationStruct vs ClassClasses imbriquées et internesRécapitulatif - Système de gestion d'étudiants8Polymorphisme
Polymorphisme : Compilation vs ExécutionSurcharge de fonctionsRetour sur les fonctions virtuellesFonctions virtuelles puresClasses abstraitesConception d'interfaces en C++Dynamic Casting & RTTIRécapitulatif - Calculateur de formes11Concepts avancés de la POO
Composition vs HéritageMixins via CRTPIdiome PimplEffacement de typeEnum Classes & Typage fortGestion des exceptions en POOHiérarchies d'exceptions personnalisées14Patrons de conception, Partie 2
Patron CommandePatron AdaptateurPatron DécorateurPatron Template MethodPatron ÉtatPatron CompositeRAII en tant que patron3Constructeurs et Destructeurs
Constructeur par défautConstructeur paramétréConstructeur de copieConstructeur de déplacementListes d'initialisation du constructeurConstructeurs déléguésAnalyse approfondie du destructeurRègle de trois / cinq / zéroRécapitulatif - Classe String6Surcharge d'opérateurs
Introduction à la surcharge d'opérateursSurcharge des opérateurs arithmétiquesSurcharge des opérateurs de comparaisonOpérateurs de fluxSurcharge de l'opérateur d'affectationSurcharge des opérateurs [] et ()Opérateurs de conversion de typeRécapitulatif - Classe Matrix9Templates
Templates de fonctionsTemplates de classesSpécialisation de templatesTemplates variadiquesBases de SFINAE & Type TraitsRécapitulatif - Conteneur générique