Menu
Coddy logo textTech

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 à 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 de 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 redéfinir 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;
    }
};

L'utilisation de override est cruciale car elle permet de détecter les erreurs au moment de la compilation. Si vous faites accidentellement une faute de frappe dans le nom de la fonction ou si vous utilisez des paramètres incorrects, le compilateur vous alertera au lieu de créer silencieusement une nouvelle fonction :

class Cat : public Animal {
public:
    void speek() override {  // Erreur de compilation : 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 une fonction final
};

Utilisez toujours override lors de la redéfinition de fonctions virtuelles. Cela documente votre intention et permet au compilateur de vérifier que vous redéfinissez réellement une fonction virtuelle existante.

challenge icon

Défi

Facile

Construisons un système de notification qui démontre la puissance des mots-clés override et final avec les fonctions virtuelles. Vous allez créer une hiérarchie de gestionnaires de notifications où certaines méthodes peuvent être personnalisées davantage et d'autres sont verrouillées pour empêcher toute modification.

Vous organiserez votre code sur trois fichiers :

  • Notifier.h : Définissez une classe de base Notifier qui représente n'importe quel expéditeur de notification :
    • Un membre protégé std::string recipient
    • 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 retourne la chaîne "Generic"
    • Un destructeur virtuel
  • EmailNotifier.h : Définissez une classe EmailNotifier qui hérite de Notifier :
    • Un membre privé std::string domain
    • Un constructeur qui prend le destinataire et le domaine, en passant le destinataire à la classe de base
    • Surchargez send() pour afficher : Emailing <recipient>@<domain>: <message>
    • Surchargez getType() et marquez-la comme final — elle doit retourner "Email"

    Ensuite, définissez une classe UrgentEmailNotifier qui hérite de EmailNotifier :

    • Un constructeur qui prend le destinataire et le domaine, en passant les deux à EmailNotifier
    • Surchargez send() pour afficher : [URGENT] Emailing <recipient>@<domain>: <message>
    • Remarque : Vous ne pouvez pas surcharger getType() ici car elle a été marquée final dans EmailNotifier
  • main.cpp : Lisez trois entrées (chacune sur une ligne séparée) :
    1. Nom du destinataire
    2. Domaine de l'e-mail
    3. Texte du message

    Créez trois objets de notification dynamiquement : un Notifier de base, un EmailNotifier, et un UrgentEmailNotifier — tous utilisant le même destinataire (et le même domaine le cas échéant). Stockez-les dans un tableau de pointeurs Notifier*.

    Bouclez sur le tableau et pour chaque notification, affichez son type en utilisant getType(), puis appelez send() avec votre message. Formatez chaque entrée comme suit :

    Type: <type>
    <send output>

    Affichez une ligne vide entre chaque notification. Libérez vos 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 3pm

Remarquez comment UrgentEmailNotifier peut surcharger send() pour personnaliser le format du message, mais il hérite du type "Email" de EmailNotifier car getType() a été marquée final. Utilisez le mot-clé override sur toutes les méthodes surchargées pour détecter toute erreur de signature au moment de la compilation.

Aide-mémoire

Le spécificateur override indique explicitement au compilateur que vous avez l'intention de redéfinir une fonction virtuelle d'une classe de base :

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;
    }
};

L'utilisation de override permet de détecter les erreurs au moment de la compilation. Si vous faites une faute de frappe dans le nom de la fonction ou si vous utilisez les mauvais paramètres, le compilateur vous alertera :

class Cat : public Animal {
public:
    void speek() override {  // Compiler error: no function to override
        std::cout << "Meow!" << std::endl;
    }
};

Le spécificateur final empêche toute redéfinition ultérieure dans les classes dérivées :

class Bulldog : public Dog {
public:
    void speak() override final {
        std::cout << "Gruff woof!" << std::endl;
    }
};

class TinyBulldog : public Bulldog {
    void speak() override {}  // Error: cannot override final function
};

Utilisez toujours override lors de la redéfinition de fonctions virtuelles pour documenter votre intention et permettre la vérification par le compilateur.

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 notificateur :
    // - Afficher "Type: " suivi du résultat de getType()
    // - Appeler send() avec le message
    // - Afficher une ligne vide entre les notificateurs (pas après le dernier)
    
    // TODO: Libérer les objets alloués dynamiquement
    
    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