Menu
Coddy logo textTech

Polymorphisme : Compilation vs Exécution

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

Le polymorphisme signifie « plusieurs formes » et constitue un concept fondamental de la POO qui permet de traiter les objets de manière uniforme tout en ayant des comportements différents. Le C++ prend en charge deux types distincts de polymorphisme, chacun étant résolu à une étape différente de l'exécution du programme.

Le polymorphisme à la compilation (également appelé polymorphisme statique) est résolu par le compilateur avant l'exécution du programme. Le compilateur détermine exactement quelle fonction appeler en se basant sur la signature de la fonction. Cela inclut la surcharge de fonctions et les templates :

void print(int x) { std::cout << "Integer: " << x << std::endl; }
void print(double x) { std::cout << "Double: " << x << std::endl; }

print(5);      // Le compilateur choisit print(int)
print(3.14);   // Le compilateur choisit print(double)

Le polymorphisme d'exécution (également appelé polymorphisme dynamique) est résolu pendant l'exécution du programme. La décision concernant la fonction à appeler dépend du type réel de l'objet, et non du type de pointeur ou de référence. Ceci est réalisé grâce aux fonctions virtuelles :

class Shape {
public:
    virtual void draw() { std::cout << "Drawing shape" << std::endl; }
};

class Circle : public Shape {
public:
    void draw() override { std::cout << "Drawing circle" << std::endl; }
};

Shape* s = new Circle();
s->draw();  // Décidé à l'exécution : "Drawing circle"

Le compromis clé : le polymorphisme à la compilation n'a aucun surcoût à l'exécution puisque les décisions sont prises lors de la compilation, tandis que le polymorphisme à l'exécution ajoute un léger coût (recherche dans la vtable) mais offre une plus grande flexibilité pour travailler avec des objets dont les types ne sont pas connus avant l'exécution.

challenge icon

Défi

Facile

Construisons un système de calculatrice qui démontre les deux types de polymorphisme côte à côte. Vous allez créer un système où le polymorphisme à la compilation gère différents types d'entrées via la surcharge de fonctions, tandis que le polymorphisme à l'exécution permet d'échanger dynamiquement différentes stratégies de calcul.

Vous organiserez votre code sur trois fichiers :

  • Calculator.h : Définissez une classe de base Calculator qui représente n'importe quelle stratégie de calcul :
    • Une méthode virtuelle calculate(int a, int b) qui retourne un int et affiche : Base calculation: <a> ? <b> (en retournant 0)
    • Un destructeur virtuel
  • Operations.h : Définissez deux classes de calculatrices dérivées qui surchargent le comportement de calcul :
    • Adder : Surchargez calculate() pour afficher Adding: <a> + <b> et retourner la somme
    • Multiplier : Surchargez calculate() pour afficher Multiplying: <a> * <b> et retourner le produit
    Les deux classes doivent utiliser le mot-clé override.
  • main.cpp : Créez un système qui présente les deux types de polymorphisme. Lisez deux entrées entières (chacune sur une ligne séparée).

    Tout d'abord, démontrez le polymorphisme à la compilation en créant trois fonctions display() surchargées :

    • display(int x) affiche : Integer value: <x>
    • display(double x) affiche : Double value: <x>
    • display(const std::string& x) affiche : String value: <x>

    Ensuite, démontrez le polymorphisme à l'exécution en créant un tableau de pointeurs Calculator* contenant une base Calculator, un Adder et un Multiplier. Parcourez le tableau et appelez calculate() sur chacun avec vos valeurs d'entrée, en affichant le résultat après chaque calcul.

    Structurez votre sortie comme suit :

    === Compile-Time Polymorphism ===
    <sorties display pour int, double, string>
    
    === Runtime Polymorphism ===
    <sorties calculate avec résultats>

    Pour la section à la compilation, appelez display() avec la première entrée en tant qu'entier, puis en tant que double (même valeur avec .5 ajouté), puis en tant que chaîne "Result". Libérez vos calculatrices allouées dynamiquement une fois terminé.

Par exemple, avec les entrées 10 et 3 :

=== Compile-Time Polymorphism ===
Integer value: 10
Double value: 10.5
String value: Result

=== Runtime Polymorphism ===
Base calculation: 10 ? 3
Result: 0
Adding: 10 + 3
Result: 13
Multiplying: 10 * 3
Result: 30

Remarquez comment le compilateur sélectionne la bonne surcharge de display() en fonction du type d'argument (décision à la compilation), tandis que la bonne méthode calculate() est déterminée par le type réel de l'objet à l'exécution via le mécanisme de vtable.

Aide-mémoire

Le C++ prend en charge deux types de polymorphisme résolus à différentes étapes :

Le polymorphisme à la compilation (polymorphisme statique) est résolu par le compilateur avant l'exécution. Il comprend la surcharge de fonctions et les templates :

void print(int x) { std::cout << "Integer: " << x << std::endl; }
void print(double x) { std::cout << "Double: " << x << std::endl; }

print(5);      // Le compilateur choisit print(int)
print(3.14);   // Le compilateur choisit print(double)

Le polymorphisme à l'exécution (polymorphisme dynamique) est résolu pendant l'exécution à l'aide de fonctions virtuelles. Le type réel de l'objet détermine quelle fonction est appelée :

class Shape {
public:
    virtual void draw() { std::cout << "Drawing shape" << std::endl; }
};

class Circle : public Shape {
public:
    void draw() override { std::cout << "Drawing circle" << std::endl; }
};

Shape* s = new Circle();
s->draw();  // Décidé à l'exécution : "Drawing circle"

Compromis : Le polymorphisme à la compilation n'a aucun surcoût à l'exécution, tandis que le polymorphisme à l'exécution ajoute un léger coût (recherche dans la vtable) mais offre une plus grande flexibilité.

Essayez vous-même

#include <iostream>
#include <string>
#include "Calculator.h"
#include "Operations.h"

// TODO: Créer trois fonctions display() surchargées :
// 1. display(int x) - affiche "Integer value: <x>"
// 2. display(double x) - affiche "Double value: <x>"
// 3. display(const std::string& x) - affiche "String value: <x>"



int main() {
    // Lire deux entrées entières
    int a, b;
    std::cin >> a;
    std::cin >> b;
    
    // === Compile-Time Polymorphism ===
    std::cout << "=== Compile-Time Polymorphism ===" << std::endl;
    // TODO: Appeler display() avec :
    // - a en tant qu'entier
    // - a en tant que double (lui ajouter 0.5)
    // - la chaîne "Result"
    
    
    std::cout << std::endl;
    
    // === Runtime Polymorphism ===
    std::cout << "=== Runtime Polymorphism ===" << std::endl;
    // TODO: Créer un tableau de pointeurs Calculator* avec 3 éléments :
    // - un Calculator de base
    // - un Adder
    // - un Multiplier
    
    // TODO: Parcourir le tableau, appeler calculate(a, b) sur chaque élément,
    // et afficher "Result: <return_value>" après chaque calcul
    
    
    // TODO: Libérer la mémoire allouée 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