Menu
Coddy logo textTech

Fonctions virtuelles et VTable

Fait partie de la section Programmation Orientée Objet du Journey C++ de Coddy. Leçon 52 sur 104.

Le mot-clé virtual résout le problème que nous avons vu dans la leçon précédente. Lorsque vous déclarez une méthode comme virtuelle, C++ détermine quelle version appeler en fonction du type réel de l’objet lors de l’exécution, et non du type du pointeur.

class Animal {
public:
    virtual void speak() {
        std::cout << "Some sound" << std::endl;
    }
};

class Dog : public Animal {
public:
    void speak() override {
        std::cout << "Woof!" << std::endl;
    }
};

Dog d;
Animal* ptr = &d;
ptr->speak();  // Sortie : Woof!

Maintenant, la méthode correcte est appelée ! Le mot-clé override est facultatif, mais recommandé. Il indique au compilateur que vous avez l’intention de remplacer une fonction virtuelle, en détectant les erreurs si les signatures ne correspondent pas.

Comment cela fonctionne-t-il ? Lorsqu’une classe possède des fonctions virtuelles, le compilateur crée une table virtuelle (vtable). Il s’agit d’une table de recherche cachée contenant des pointeurs vers les fonctions virtuelles de la classe. Chaque objet possédant des fonctions virtuelles stocke un pointeur caché (vptr) vers la vtable de sa classe.

Lorsque vous appelez une fonction virtuelle via un pointeur, le programme recherche l’adresse de fonction correcte dans la vtable au moment de l’exécution. Cela s’appelle la liaison dynamique. Cela ajoute une légère surcharge par rapport aux appels de fonctions ordinaires, mais permet un comportement polymorphe puissant.

Une règle importante : si une classe possède des fonctions virtuelles et est utilisée comme classe de base, son destructeur doit également être virtuel. Cela garantit un nettoyage correct lors de la suppression d’objets dérivés via des pointeurs vers la classe de base :

class Animal {
public:
    virtual ~Animal() {}  // Destructeur virtuel
    virtual void speak() {}
};
challenge icon

Défi

Facile

Construisons un système de lecteur multimédia qui démontre la puissance des fonctions virtuelles et du polymorphisme à l'exécution. Vous allez créer une hiérarchie de types multimédias où chacun est lu différemment, et voir comment les fonctions virtuelles permettent d'obtenir le comportement correct même lorsqu'elles sont appelées via des pointeurs vers la classe de base.

Vous organiserez votre code sur trois fichiers :

  • Media.h : définir une classe de base Media qui représente tout média lisible :
    • un membre std::string title protégé
    • un constructeur qui reçoit un titre et le stocke
    • une méthode virtual play() qui affiche : Playing media: <title>
    • un destructeur virtuel qui affiche : Media [<title>] destroyed
  • AudioTrack.h : définir une classe AudioTrack qui hérite publiquement de Media :
    • un membre privé std::string artist
    • un constructeur qui reçoit un titre et un artiste, transmet le titre à la classe de base et stocke l'artiste
    • redéfinir play() en utilisant le mot-clé override pour afficher : Playing audio: <title> by <artist>
    • un destructeur qui affiche : AudioTrack [<title>] destroyed
  • main.cpp : lire trois entrées (chacune sur une ligne distincte) :
    1. titre de la piste audio (chaîne)
    2. nom de l'artiste (chaîne)
    3. titre de la vidéo (chaîne)

    Définir directement dans main.cpp une classe VideoClip qui hérite publiquement de Media :

    • un membre privé int duration (en secondes)
    • un constructeur qui reçoit un titre et une durée (avec une durée par défaut de 120)
    • redéfinir play() pour afficher : Playing video: <title> (<duration>s)
    • un destructeur qui affiche : VideoClip [<title>] destroyed

    Créer un tableau de trois pointeurs Media* dans la portée d'un bloc. Allouer dynamiquement :

    • un objet de base Media avec le titre "Generic Media"
    • un objet AudioTrack avec le titre et l'artiste saisis
    • un objet VideoClip avec le titre de la vidéo saisi

    Parcourir le tableau et appeler play() sur chaque pointeur. Supprimer ensuite tous les objets dans l'ordre inverse. Après le bloc, afficher : Playback complete!

Par exemple, avec les entrées Bohemian Rhapsody, Queen et Nature Documentary :

Playing media: Generic Media
Playing audio: Bohemian Rhapsody by Queen
Playing video: Nature Documentary (120s)
VideoClip [Nature Documentary] destroyed
Media [Nature Documentary] destroyed
AudioTrack [Bohemian Rhapsody] destroyed
Media [Bohemian Rhapsody] destroyed
Media [Generic Media] destroyed
Playback complete!

Remarquez comment l'appel de play() via des pointeurs Media* invoque la méthode de la classe dérivée appropriée grâce aux fonctions virtuelles. Observez également comment le destructeur virtuel garantit un nettoyage correct : les destructeurs de la classe dérivée et de la classe de base sont tous deux exécutés lors de la suppression via un pointeur vers la classe de base.

Essayez vous-même

#include <iostream>
#include <string>
#include "Media.h"
#include "AudioTrack.h"

using namespace std;

// TODO: Définir la classe VideoClip ici qui hérite publiquement de Media
// - Membre privé int duration (en secondes)
// - Le constructeur prend title et duration (duration par défaut à 120)
// - Override play() to print: Playing video: <title> (<duration>s)
// - Destructor prints: VideoClip [<title>] destroyed

class VideoClip : public Media {
private:
    int duration;

public:
    // TODO: Implémenter le constructeur
    VideoClip(const std::string& t, int d = 120) : Media(t) {
        // TODO: Stocker la duration
    }

    // TODO: Redéfinir la méthode play()
    void play() override {
        // TODO: Implémenter cette méthode
    }

    // TODO: Implémenter le destructeur
    ~VideoClip() {
        // TODO: Implémenter ce destructeur
    }
};

int main() {
    // Lire les entrées
    string audioTitle;
    string artist;
    string videoTitle;
    
    getline(cin, audioTitle);
    getline(cin, artist);
    getline(cin, videoTitle);

    // TODO: Créer un bloc de portée avec des accolades
    // À l'intérieur du bloc :
    // 1. Créer un tableau de trois pointeurs Media*
    // 2. Allouer dynamiquement :
    //    - A base Media object with title "Generic Media"
    //    - Un objet AudioTrack avec le titre et l'artiste en entrée
    //    - Un objet VideoClip avec le titre vidéo en entrée
    // 3. Parcourir le tableau et appeler play() sur chaque pointeur
    // 4. Supprimer tous les objets dans l'ordre inverse

    // TODO: After the block, print: Playback complete!
    
    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

Entraînez-vous par vous-même : Compilateur C++ en ligne