Série 21 (niveau 0) :
Polymorphisme
Exercice 0 : reprise de l'exemple du cours (Polymorphisme, niveau 0)
Introduction
Le but de cet exercice est d'illustrer la notion de polymorphisme en utilisant une collection hétérogène de véhicules.
Dans le fichier aeroport.cc, commencez par recopier les définitions des classes Vehicule, Avion et Voiture que vous aviez définie la semaine dernière dans la série 19. Repartez si nécessaire du corrigé et du code source suivant.
On souhaite maintenant créer une classe Aeroport (remplaçant le garage et le hangar) ayant à gérer un ensemble de véhicules constitué à la fois de voitures et d'avions.
[Essayez de le faire par vous même avant de regarder la solution qui suit]
Sans polymorphisme, du fait que l'on manipule deux types d'objets différents, on est obligé de créer deux tableau différents (comme nous l'avions fait dans le main()).
On souhaiterait maintenant plutôt pouvoir écrire notre classe comme suit :
[NOTE : On pourrait aussi faire hériter la classe Aeroport de la classe vector<Vehicule>. Mais c'est une autre histoire. ]
class Aeroport
{
public:
void affiche_vehicules(ostream&);
void ajouter_vehicules(const Vehicule&);
void vider_vehicules();
protected:
vector<Vehicule> vehicules;
};
et aussi pouvoir indifféremment mettre dans notre vecteur vehicules des voitures et des avions.
Bref, en deux mots, utiliser le polymorphisme et la résolution dynamique des liens.
Il faut alors pour cela utiliser des références sur des objets plutôt que les objets eux-même, et donc utiliser un vecteur de pointeurs.
Notre classe Aeroport s'écrit alors :
class Aeroport
{
public:
void affiche_vehicules(ostream&) const;
void ajouter_vehicules(Vehicule*);
void vider_vehicules();
protected:
vector<Vehicule*> vehicule;
};
void Aeroport::affiche_vehicule(ostream& out) const
{
for (unsigned int i(0); i < vehicules.size(); i++)
{
vehicules[i]->calculePrix();
vehicules[i]->affiche(out);
}
}
void Aeroport::ajouter_vehicule(Vehicule* v)
{
vehicules.push_back(v);
}
void Aeroport::vider_vehicules()
{
vehicules.clear();
}
Pour que la résolution dynamique des liens puisse être mise en oeuvre, il faut aussi que les méthodes que l'on invoque sur les objets de type Vehicule soient virtuelles.
De cette façon, si vehicules[i]est un avion vehicule[i].afficher() fera appel à la méthode d'affichage de la classe Avion et non la méthode de Vehicule.
Notre classe Vehicule devient donc :
class Vehicule
{
public:
Vehicule(string marque, unsigned int date, double prix);
virtual void calculePrix();
virtual void affiche(ostream&) const;
virtual ~Vehicule() {}
protected:
string marque;
unsigned int date_achat;
double prix_achat;
double prix_courant;
};
Voilà, notre classe Aeroport est maintenant utilisable.
Pour que le codage en soit complètement satisfaisant, il faudrait cependant pouvoir
offrir le moyen d'éventuellement pouvoir libérer la mémoire des objets mis dans la
collection (au cas où la fonction qui les a créés et ajouté à la collection souhaite les
supprimer).
(En tout rigueur il faudrait aussi pouvoir en supprimer 1 élément précis et fournir une
méthode de copie profonde au cas où).
Ajoutez une méthode supprimer_vehicules qui effectue ce nettoyage mémoire.
Vous pouvez alors tester avec le main suivant :
int main()
{
Aeroport gva;
gva.ajouter_vehicule(new Voiture("Peugeot", 1998, 147325.79, 2.5, 5, 180.0, 12000));
gva.ajouter_vehicule(new Voiture("Porsche", 1985, 250000.00, 6.5, 2, 280.0, 81320));
gva.ajouter_vehicule(new Avion("Cessna", 1972, 1230673.90, HELICES, 250));
gva.ajouter_vehicule(new Avion("Nain Connu", 1992, 4321098.00, REACTION, 1300));
gva.ajouter_vehicule(new Voiture("Fiat", 2001, 7327.30, 1.6, 3, 65.0, 3000));
gva.affiche_vehicules(cout);
// pour être propre, le main() demande (à gva) de libérer
// la mémoire qu'il (main) a alloué (et c'est à lui (main) de le faire
// pas au destructeur de gva qui n'a pas alloué cette mémoire)
gva.supprimer_vehicules();
return 0;
}
Vous pouvez trouver ici le code complet de l'exemple.
[NOTE : Comme dans toute collection hétérogène construite par pointeurs, il faut
faire attention à la gestion de la mémoire.
Nous avons volontairement écarté ici l'aspect allocation dynamique (qui n'est pas
nécessaire mais souvent utile) ici, et laissez le soin à la fonction main() de
s'en charger de façon simple, mais si cette allocation dynamique devait être géré au niveau
de la classe Aeroport, il faudrait bien entendu le faire proprement, avec
constructeur de copie, copie profonde, libération par le destructeur, surcharge de
l'opérateur d'affectation (=), etc. ]