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.
Défi
FacileConstruisons 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 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éfinir une classeDeveloperqui utilise l’héritage public virtuel deEmployee:- Un membre
std::string languageproté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
- Un membre
Manager.h: Définir une classeManagerqui utilise l’héritage public virtuel deEmployee:- Un membre
int teamSizeproté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
- Un membre
main.cpp: Lire quatre entrées, chacune sur une ligne distincte :- Nom (chaîne de caractères)
- Identifiant de l’employé (entier)
- Langage de programmation (chaîne de caractères)
- Taille de l’équipe (entier)
Définir une classe
TeamLeadqui hérite publiquement à la fois 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éer un objet
TeamLeaddans un bloc, appelershowRole(), 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;
}
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 externesGénération et compilation en C++Fichiers d’en-tête et fichiers sourceEspaces de noms et portéeIntroduction à la POO en C++Classes et objetsLe pointeur « this »Méthodes (fonctions membres)Attributs (membres de données)Bases des constructeurs et destructeursRécapitulatif - Calculatrice simple4Propriétés de classe
Membres d’instance et statiquesAccesseurs et mutateursFonctions membres constMot-clé mutableMéthodes et variables statiquesFonctions et classes amiesRécapitulatif - Gestionnaire de compte bancaire7Héritage
Héritage de baseNiveaux d’accès de l’héritageOrdre d’appel des constructeurs et destructeursRedéfinition des méthodesFonctions virtuelles et VTableHéritage multipleHéritage virtuelRécapitulatif : hiérarchie des employés10Vue d’ensemble de la STL
Vue d’ensemble et philosophie de la STLConteneurs de la STLItérateursAlgorithmes de la STLFoncteurs et expressions lambdaRécapitulatif - fréquence des mots13Modèles de conception, partie 1
Introduction aux modèles de conceptionModèle SingletonFabrique et fabrique abstraiteModèle BuilderModèle ObserverModèle Strategy2Gestion de la mémoire
Mémoire de pile ou de tasPointeurs et référencesMémoire dynamique (new/delete)Pointeurs intelligents 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 de l’informationStruct vs classeClasses imbriquées et internesRécapitulatif - Système de gestion des dossiers étudiants8Polymorphisme
Polymorphisme à la compilation vs à l’exécutionSurcharge de fonctionsRetour sur les fonctions virtuellesFonctions virtuelles puresClasses abstraitesConception d’interfaces en C++Conversion dynamique et RTTIRécapitulatif - Calculateur de formes3Constructeurs et destructeurs
Constructeur par défautConstructeur paramétréConstructeur de copieConstructeur de déplacementListes d’initialisation des constructeursConstructeurs déléguésApprofondissement des destructeursRègle des trois / cinq / zéroRécapitulatif - Classe String6Surcharge des opérateurs
Introduction à la surcharge des 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 des templatesTemplates variadiquesBases de SFINAE et des traits de typesRécapitulatif - Conteneur génériqueEntraînez-vous par vous-même : Compilateur C++ en ligne