Correction 21
Collection hétérogènes
Exercice 1 : encore des figures géométriques (polymorphisme, niveau 2)
Prototypez et définissez les classes [...]
#include <iostream> // pour cout using namespace std; // pour écrire cout au lieu de std::cout class Figure { public: virtual void affiche () const = 0; virtual Figure* copie() const = 0; };
[...] Trois sous-classes (héritage publique) de Figure : Cercle, Carre et Triangle.
class Cercle : public Figure { }; class Carre : public Figure { }; class Triangle : public Figure { };
Une classe nommée Dessin, qui modélise une collection de figures. [...]
class Dessin : private vector<Figure*> { public: void ajouteFigure(); };
Plutôt que d'encapsuler la collection dans la classe, je préfère ici dire que la classe Dessin EST UNE collection, et donc hériter de la classe vector. Pour pouvoir bénéficier du polymorphisme, les constituants de cette collection doivent être des pointeurs.
[...] définissez les attributs requis pour modéliser les objets correspondant
class Cercle : public Figure { private: double rayon; }; class Carre : public Figure { private: double cote; }; class Triangle : public Figure { private: double base; double hauteur; };
Définissez également, pour chacune de ces sous-classe, un constructeur pouvant être utilisé comme constructeur par défaut, un constructeur de copie et un destructeur. [...]
class Cercle : public Figure { public: Cercle(double x = 0.0) { cout << "Et hop, un cercle de plus !" << endl; rayon = x; } Cercle(const Cercle& c) { cout << "Et encore un cercle qui fait des petits !" << endl; rayon = c.rayon; } ~Cercle() { cout << "le dernier cercle ?" << endl; } private: double rayon; }; class Carre : public Figure { public: Carre(double x = 0.0) { cote = x; cout << "Coucou, un carré de plus !" << endl; } Carre(const Carre& c) { cout << "Et encore un carré qui fait des petits !" << endl; cote = c.cote; } ~Carre() { cout << "bou... un carré de moins." << endl; } private: double cote; }; class Triangle : public Figure { public: Triangle(double h = 0.0, double b = 0.0) { base = b; hauteur = h; cout << "Un Triangle est arrivé !" << endl; } Triangle(const Triangle& t) { cout << "Et encore un triangle qui fait des petits !" << endl; base = t.base; hauteur = t.hauteur; } ~Triangle() { cout << "La fin du triangle." << endl; } private: double base; double hauteur; };
Définissez la méthode de copie en utilisant le constructeur de copie.
Cette partie, quoique finalement simple, est peut être d'un abord
plus difficile.
Ne vous laissez pas démonter par l'apparente difficulté
conceptuelle et, une fois de plus, décomposez le problème :
- Que veut-on ?
Retourner le pointeur sur une copie de l'objet :
Figure* copie() const { }
- comment fait-on une copie ?
ben .. en utilisant le constructeur de copie.
Par exemple pour la classe Cercle, cela s'écrit Cercle(...)
- De qui fait-on une copie ?
de nous-même.
Comment ça s'écrit "nous-même" ?
*this (contenu de l'objet pointé par this).
On a donc : Cercle(*this)
- Que veut-on de plus ?
Que la copie soit effectivement placée en mémoire et on veut en retourner l'adresse (allocation dynamique).
Cela se fait avec new
Et donc finalement on aboutit à :
class Cercle : public Figure { ... Figure* copie() const { return new Cercle(*this); } ... }; class Carre : public Figure { ... Figure* copie() const { return new Carre(*this); } ... }; class Triangle : public Figure { ... Figure* copie() const { return new Triangle(*this); } ... };
Finalement, définissez la méthode virtuelle affiche, affichant le type de l'instance et la valeur de ses attributs.
Trivial :
class Cercle : public Figure { ... void affiche() const { cout << "Un cercle de rayon " << rayon << endl; } ... }; class Carre : public Figure { ... void affiche() const { cout << "Un carre de de coté " << cote << endl; } ... }; class Triangle : public Figure { ... void affiche() const { cout << "Un triangle " << base << "x" << hauteur << endl; } ... };
Ajoutez un destructeur explicite pour la classe Dessin [...]
~Dessin() { cout << "Le dessins s'efface..." << endl; for (unsigned int i(0); i < size(); ++i) delete (*this)[i]; }
Notez que puisque que la classe Dessin hérite de vector elle possède elle-même une méthode size et un opérateur [].
Prototypez et définissez ensuite les méthodes suivantes à la classe Dessin : [...]
class Dessin : private vector<Figure*> { public: ~Dessin() { cout << "Le dessins s'efface..." << endl; for (unsigned int i(0); i < size(); ++i) delete (*this)[i]; } void ajouteFigure(const Figure& fig) { push_back(fig.copie()); } void affiche() const { cout << "Je contiens :" << endl; for (unsigned int i(0); i < size(); ++i) { (*this)[i]->affiche(); } } };
Votre programme devrait indiquer que le destructeur du dessin est invoqué... mais pas les destructeurs des figures stockées dans le dessin. Pourquoi ?
Car le destructeur n'est pas virtuel. Le compilateur vous a d'ailleurs avertit par les warnings.
Pour y remédier, il suffit d'ajouter le mot clef virtual devant.
Voici ici le code complet.
[...]
le système doit interrompre prématurément votre programme,
en vous adressant un message hargneux [...]
Quel est, à votre avis, le motif pour un tel comportement ?
Ceci est le cas classique de la libération incongrue de mémoire
par une copie passagère de l'objet. Voir le cours pour plus de
détails, mais en deux mot, tmp crée une copie de
img
qui est détruite à de la fin de la fonction et libère la mémoire
pointée, laquelle est encore utilisée par l'objet passé en argument
comme img.
La prochaine tentative d'accès à cette mémoire via cet objet (du main()) provoque
une erreur comme indiquée.