• 10 heures
  • Difficile

Ce cours est visible gratuitement en ligne.

course.header.alt.is_video

course.header.alt.is_certifying

J'ai tout compris !

Mis à jour le 10/02/2022

Mettez en œuvre le polymorphisme

Vous venez de découvrir une notion un peu complexe : le polymorphisme. C'est le moment de passer à une application concrète pour comprendre comme l'utiliser par la suite.

Gérez les collections hétérogènes

Je vous ai dit au chapitre précédent que nous voulions créer un programme de gestion d'un garage. Par conséquent, nous allons devoir gérer une collection de voitures et de motos. Nous ferons donc appel à… des tableaux dynamiques !

vector<Voiture> listeVoitures;
vector<Moto> listeMotos;

Bien ! Mais pas optimal. Si notre ami garagiste commence à recevoir des commandes pour des scooters, des camions, des fourgons, des vélos, etc., il va falloir déclarer beaucoup de  vectors  . Cela veut dire qu'il va falloir apporter de grosses modifications au code à chaque apparition d'un nouveau type de véhicule.

Utilisez des pointeurs

Il serait bien plus judicieux de mettre le tout dans un seul tableau ! Comme les motos et les voitures sont des véhicules, on peut déclarer un tableau de véhicules et mettre des motos dedans. Mais si nous procédons ainsi, nous allons alors perdre la vraie nature des objets.

Souvenez-vous des deux ingrédients du polymorphisme : il nous faut donc un tableau de pointeurs ou un tableau de références. On ne peut pas créer un tableau de références (les références ne sont que des étiquettes), nous allons donc devoir utiliser des pointeurs.

int main()
{
    vector<Vehicule*> listeVehicules;
    return 0;
}

Utilisez la collection

Commençons par remplir notre tableau. Comme nous allons accéder à nos véhicules uniquement via les pointeurs, nous n'avons pas besoin d'étiquettes sur nos objets, et nous pouvons utiliser l'allocation dynamique pour les créer. En plus, cela nous permet d'avoir directement un pointeur à mettre dans notre  vector  .

int main()
{
    vector<Vehicule*> listeVehicules;

    listeVehicules.push_back(new Voiture(15000, 5));
    //J'ajoute à ma collection de véhicules une voiture
    //Valant 15000 euros et ayant 5 portes
    listeVehicules.push_back(new Voiture(12000, 3));  //…
    listeVehicules.push_back(new Moto(2000, 212.5));
    //Une moto à 2000 euros allant à 212.5 km/h

    //On utilise les voitures et les motos
   
    return 0;
}

Voici, schématiquement, notre tableau :

Une collection hétérogène
Une collection hétérogène

Les voitures et motos ne sont pas réellement dans les cases. Ce sont des pointeurs. Mais en suivant les flèches, on accède aux véhicules.

Bien ! Mais nous venons de faire une grosse faute !

Nous allons donc devoir faire appel à une boucle pour libérer la mémoire allouée :

int main()
{
    vector<Vehicule*> listeVehicules;

    listeVehicules.push_back(new Voiture(15000, 5));
    listeVehicules.push_back(new Voiture(12000, 3));
    listeVehicules.push_back(new Moto(2000, 212.5));  

    //On utilise les voitures et les motos
   
    for(int i(0); i<listeVehicules.size(); ++i)
    {
        delete listeVehicules[i];  //On libère la i-ème case mémoire allouée
        listeVehicules[i] = 0;  //On met le pointeur à 0 pour éviter les soucis
    }

    return 0;
}

Il ne nous reste plus qu'à utiliser nos objets. Comme c'est un exemple basique, ils ne savent faire qu'une seule chose : afficher des informations. Mais essayons quand même !

int main()
{
    vector<Vehicule*> listeVehicules;

    listeVehicules.push_back(new Voiture(15000, 5));
    listeVehicules.push_back(new Voiture(12000, 3));
    listeVehicules.push_back(new Moto(2000, 212.5));  

    listeVehicules[0]->affiche();
    //On affiche les informations de la première voiture
    
    listeVehicules[2]->affiche();
    //Et celles de la moto
   
    for(int i(0); i<listeVehicules.size(); ++i)
    {
        delete listeVehicules[i];  //On libère la i-ème case mémoire allouée
        listeVehicules[i] = 0;  //On met le pointeur à 0 pour éviter les soucis
    }

    return 0;
}

Je vous invite, comme toujours, à tester. Voici ce que vous devriez obtenir :

Ceci est une voiture avec 5 portes valant 15000 euros.
Ceci est une moto allant a 212.5 km/h et valant 2000 euros.

Ce sont les bonnes versions des méthodes qui sont appelées ! Nous avons des méthodes virtuelles pointeurs (ingrédient 1) et des pointeurs (ingrédient 2).

Je vous propose d'améliorer un peu ce code en ajoutant les éléments suivants :

  • Une classe Camion qui aura comme attribut le poids qu'il peut transporter.

  • Un attribut représentant l'année de fabrication du véhicule. Ajoutez aussi des méthodes pour afficher cette information.

  • Une classe Garage qui aura comme attribut le vector<Vehicule*> et proposerait des méthodes pour ajouter/supprimer des véhicules, ou pour afficher des informations sur tous les éléments contenus.

  • Une méthode nbrRoues()  qui renvoie le nombre de roues des différents véhicules.

Après ce léger entraînement, terminons ce chapitre avec une évolution de notre petit programme.

Utilisez les fonctions virtuelles pures

Avez-vous essayé de programmer la méthode nbrRoues() ? Si ce n'est pas le cas, il est encore temps de le faire. Elle va beaucoup nous intéresser par la suite.

Le problème des roues

Comme c'est un peu répétitif, je vous donne ma version de la fonction pour les classes Vehicule et Voiture uniquement :

class Vehicule
{
    public:
    Vehicule(int prix);           
    virtual void affiche() const;
    virtual int nbrRoues() const;  //Affiche le nombre de roues du véhicule
    virtual ~Vehicule();         

    protected:
    int m_prix;
};

class Voiture : public Vehicule
{
    public:
    Voiture(int prix, int portes);
    virtual void affiche() const;
    virtual int nbrRoues() const;  //Affiche le nombre de roues de la voiture
    virtual ~Voiture();

    private:
    int m_portes;
};

Du côté du .hpp  , pas de souci. C'est le corps des fonctions qui risque de poser problème :

int Vehicule::nbrRoues() const
{  
  //Que mettre ici ????
}

int Voiture::nbrRoues() const
{
    return 4;
}

Vous l'aurez compris, on ne sait pas vraiment quoi mettre dans la version Vehicule de la méthode. Les voitures ont 4 roues et les motos 2 mais, pour un véhicule en général, on ne peut rien dire ! On aimerait bien ne rien mettre ici, ou carrément supprimer la fonction puisqu'elle n'a pas de sens.

Mais si on ne déclare pas la fonction dans la classe mère, alors on ne pourra pas l'utiliser depuis notre collection hétérogène. Il nous faut donc la garder, ou au minimum dire qu'elle existe mais qu'on n'a pas le droit de l'utiliser. On souhaiterait ainsi dire au compilateur :

"Dans toutes les classes filles de Vehicule  , il y a une fonction nommée nbrRoues() qui renvoie un int et qui ne prend aucun argument mais, dans la classe Vehicule  , cette fonction n'existe pas."

class Vehicule
{
    public:
    Vehicule(int prix);           
    virtual void affiche() const;
    virtual int nbrRoues() const = 0;  //Affiche le nombre de roues du véhicule
    virtual ~Vehicule();         

    protected:
    int m_prix;
};

Et évidemment, on n'a rien à écrire dans le .cpp puisque, justement, on ne sait pas quoi y mettre. On peut supprimer complètement la méthode. L'important étant que son prototype soit présent dans le .hpp  .

Les classes abstraites

Une classe qui possède au moins une méthode virtuelle pure est une classe abstraite. Notre classe Vehicule est donc une classe abstraite.

Pourquoi donner un nom spécial à ces classes ?

Oui, oui, vous avez bien lu ! La ligne suivante ne compilera pas :

Vehicule v(10000); //Création d'un véhicule valant 10000 euros.

Par contre, je peux tout à fait écrire le code suivant :

int main()
{
    Vehicule* ptr(0);  //Un pointeur sur un véhicule
    
    Voiture caisse(20000,5);
    //On crée une voiture
    //Ceci est autorisé puisque toutes les fonctions ont un corps
    
    ptr = &caisse;  //On fait pointer le pointeur sur la voiture

    cout << ptr->nbrRoues() << endl;
    //Dans la classe fille, nbrRoues() existe, ceci est donc autorisé

    return 0;
}

Ici, l'appel à la méthode nbrRoues() est polymorphique, puisque nous avons un pointeur et que notre méthode est virtuelle. C'est donc la versionVoiture qui est appelée. Donc même si la version Vehicule n'existe pas, il n'y a pas de problème.

Si l'on veut créer une nouvelle sorte de Vehicule  ( Camion  , par exemple), on sera obligé de redéfinir la fonction nbrRoues()  , sinon cette dernière sera virtuelle pure par héritage et, par conséquent, la classe sera abstraite elle aussi.

À vous de jouer !

Comme le polymorphisme est une notion assez complexe, je vous propose de pratiquer un peu pour vous entraîner. Vous allez devoir créer une classe mère  Figure  qui permettra ensuite de créer 4 classes filles afin de travailler l’héritage et le polymorphisme.

Votre mission :

  1. Créer la classe abstraite  Figure  contenant les méthodes virtuelles :

    •  afficher()  qui affiche le type de la classe. Exemple :  Je suis une figure  ;

    • la méthode virtuelle pure  perimetre()  ;

    • la méthode virtuelle pure  aire() .

  2. Créer les 4 classes filles   ( Triangle ,  Carre ,  Rectangle ,  Cercle ) qui hériteront de la classe mère  Figure  et qui définissent les trois méthodes virtuelles de la classe  Figure . 

  3. Créer les constructeurs des 4 classes filles avec les attributs :

    • Triangle  : base et hauteur ;

    • Carre  : longueur ;

    • Rectangle  : longueur et largeur ;

    • Cercle  : rayon.

  4. Définir l’ensemble des méthodes virtuelles.

  5. Créer un vecteur de  Figure  et ajouter une instance de chaque classe fille.

  6. Enfin à l’aide d’une boucle  for  , appeler chacune des méthodes :  affiche()  ,  perimetre()  et  aire()  .

Voici une proposition de correction avec deux classes filles :  Carre  et  Cercle :

En résumé

  • Si l'on ne sait pas quoi mettre dans le corps d'une méthode de la classe mère, on peut la déclarer virtuelle pure.

  • Une classe avec des méthodes virtuelles pures est dite abstraite. On ne peut pas créer d'objet à partir d'une telle classe.

  • Une méthode virtuelle peut être redéfinie dans une classe fille. Une méthode virtuelle pure doit être redéfinie dans une classe fille.

Dans le prochain chapitre, vous allez voir comment utiliser les éléments statiques et l'amitié. Cela ne vous parle pas ? Tant mieux, on y va !

Exemple de certificat de réussite
Exemple de certificat de réussite