Retour sur les fonctions virtuelles
Fait partie de la section Programmation Orientée Objet du Journey C++ de Coddy. Leçon 58 sur 104.
Maintenant que nous comprenons la différence entre le polymorphisme à la compilation et le polymorphisme à l’exécution, examinons de plus près les fonctions virtuelles et le mot-clé override qui les rend plus sûres à utiliser.
Lorsque vous marquez une fonction comme virtual dans une classe base, les classes dérivées peuvent fournir leur propre implémentation. Le spécificateur override indique explicitement au compilateur que vous avez l’intention de remplacer une fonction virtuelle :
class Animal {
public:
virtual void speak() {
std::cout << "Some sound" << std::endl;
}
virtual ~Animal() = default;
};
class Dog : public Animal {
public:
void speak() override {
std::cout << "Woof!" << std::endl;
}
};Utiliser override est essentiel, car cela permet de détecter les erreurs lors de la compilation. Si tu fais accidentellement une faute d’orthographe dans le nom de la fonction ou utilises de mauvais paramètres, le compilateur t’en avertira au lieu de créer silencieusement une nouvelle fonction :
class Cat : public Animal {
public:
void speek() override { // Erreur du compilateur : aucune fonction à redéfinir
std::cout << "Meow!" << std::endl;
}
};Le spécificateur final empêche toute redéfinition ultérieure. Utilisez-le lorsqu’une classe dérivée doit être la dernière à redéfinir une fonction particulière :
class Bulldog : public Dog {
public:
void speak() override final {
std::cout << "Gruff woof!" << std::endl;
}
};
class TinyBulldog : public Bulldog {
void speak() override {} // Erreur : impossible de redéfinir la fonction final
};Utilisez toujours override lorsque vous redéfinissez des fonctions virtuelles. Cela documente votre intention et permet au compilateur de vérifier que vous redéfinissez effectivement une fonction virtuelle existante.
Défi
FacileConstruisons un système de notification qui démontre la puissance des mots-clés override et final avec des fonctions virtuelles. Vous allez créer une hiérarchie de gestionnaires de notifications, dans laquelle certaines méthodes peuvent être personnalisées davantage tandis que d’autres sont verrouillées pour empêcher toute modification.
Vous organiserez votre code dans trois fichiers :
Notifier.h: définir une classe de baseNotifierqui représente n’importe quel expéditeur de notifications :- Un membre
std::string recipientprotégé - Un constructeur qui prend et stocke le nom du destinataire
- Une méthode virtuelle
send(const std::string& message)qui affiche :Notifying <recipient>: <message> - Une méthode virtuelle
getType()qui renvoie la chaîne"Generic" - Un destructeur virtuel
- Un membre
EmailNotifier.h: définir une classeEmailNotifierqui hérite deNotifier:- Un membre privé
std::string domain - Un constructeur qui prend le destinataire et le domaine, en transmettant le destinataire à la classe de base
- Redéfinir
send()pour afficher :Emailing <recipient>@<domain>: <message> - Redéfinir
getType()et le marquerfinal. Il doit renvoyer"Email"
Définir ensuite une classe
UrgentEmailNotifierqui hérite deEmailNotifier:- Un constructeur qui prend le destinataire et le domaine, en transmettant les deux à
EmailNotifier - Redéfinir
send()pour afficher :[URGENT] Emailing <recipient>@<domain>: <message> - Remarque : vous ne pouvez pas redéfinir
getType()ici, car il a été marquéfinaldansEmailNotifier
- Un membre privé
main.cpp: lire trois entrées, chacune sur une ligne distincte :- Nom du destinataire
- Domaine de l’adresse e-mail
- Texte du message
Créer dynamiquement trois objets de notification : un
Notifierde base, unEmailNotifieret unUrgentEmailNotifier, tous avec le même destinataire (et le même domaine lorsque cela s’applique). Les stocker dans un tableau de pointeursNotifier*.Parcourir le tableau et, pour chaque objet de notification, afficher son type à l’aide de
getType(), puis appelersend()avec votre message. Formater chaque entrée ainsi :Type: <type> <send output>Afficher une ligne vide entre chaque objet de notification. Libérer les objets alloués dynamiquement une fois terminé.
Par exemple, avec les entrées Alice, company.com et Meeting at 3pm :
Type: Generic
Notifying Alice: Meeting at 3pm
Type: Email
Emailing Alice@company.com: Meeting at 3pm
Type: Email
[URGENT] Emailing Alice@company.com: Meeting at 3pmRemarquez comment UrgentEmailNotifier peut redéfinir send() pour personnaliser le format du message, mais hérite du type "Email" de EmailNotifier, car getType() a été marqué final. Utilisez le mot-clé override sur toutes les méthodes redéfinies afin de détecter toute incompatibilité de signature au moment de la compilation.
Essayez vous-même
#include <iostream>
#include <string>
#include "Notifier.h"
#include "EmailNotifier.h"
using namespace std;
int main() {
// Lire les entrées
string recipient;
string domain;
string message;
getline(cin, recipient);
getline(cin, domain);
getline(cin, message);
// TODO: Créer un tableau de pointeurs Notifier* avec 3 éléments
// TODO: Créer dynamiquement :
// - Un Notifier de base avec le recipient
// - Un EmailNotifier avec recipient et domain
// - Un UrgentEmailNotifier avec recipient et domain
// TODO: Parcourir le tableau et pour chaque notifier :
// - Afficher "Type: " suivi du résultat de getType()
// - Appeler send() avec le message
// - Afficher une ligne vide entre les notifiers (pas après le dernier)
// TODO: Nettoyer les objets alloués dynamiquement
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