Correction Série 4
Correction Série 4
Correction Série 4
Lebutdecetexerciceestdevousillustrerlesproblèmesquipeuventseposerlorsquel'onveutmanipulerdesobjetsdefaçonp
olymorphique(unobjetpouvantsesubstitueràunautreobjet).
Ils'agitdemanipulerquelquesformesgéométriquessimplesenleurassociantuneméthodequiafficheleurdescription.
DéfinissezuneclasseFormeenladotantd'uneméthodevoiddescription()quiafficheàl'écran:«Ceciestuneforme!
».
AjoutezauprogrammeuneclasseCerclehéritantdelaclasseForme,etpossédantlaméthodevoiddescription()qui
afficheàl'écran:«Ceciestuncercle.». int main() {
Recopiezensuitelafonctionmain()suivante,ettestezvotreprogramme: Forme f;
Ajoutezmaintenantceciàlafindelafonctionmain(): Cercle c;
f.description();
Formef2(c);f2.description(); c.description();
return 0;
Testezensuiteànouveauvotreprogramme. }
Voyez-vousvulanuance?Pourquoia-t-oncefonctionnement? int main() {
Forme f;
Continuonsnosprospections... Cercle c;
affichageDesc(f);
Ajoutezencoreauprogrammeunefonction: affichageDesc(c);
return 0;
voidaffichageDesc(Forme&f) }
quiafficheladescriptiondelaformepasséeenargumentenutilisantsaméthodedescription().
Finalement,modifiezlemainainsi:
Testezleprogramme...Lerésultatvoussemble-t-ilsatisfaisant?Pourquoi?
Modifiezleprogramme(ajoutez1seulmot)pourquelerésultatsoitplusconformeàcequel'onpourraitattendre.
Formesabstraites
Recopiezleprogrammeprécédentdanslefichierformesabstraites.cpp,etmodifiezlaclasseFormedemanièreàenfaireu
neclasseabstraiteenluiajoutantlaméthodevirtuellepuredoubleaire()permettantdecalculerl'aired'uneforme.
ÉcrivezégalementuneclasseTriangleetmodifiezlaclasseCercleexistantehéritanttoutesdeuxdelaclasseForme,etimp
lémentantlesméthodesaire()etdescription().
Vousl'aurezdeviné,laclasseTriangleauracommeattributsbaseethauteurainsiqu'unconstructeuradéquat,etCercleau
racommeseulattributrayon(ainsiqu'unconstructeuradéquat).
ModifiezlafonctionaffichageDescpourqu'elleaffiche,enplus,l'airedelaformepasséeenparamètreettestezaveclafonct
ionmainsuivante:
int main() {
Classe Figure
Cercle c(5);
Définissez les classes suivantes :
Triangle t(10, 2);
La classe abstraite Figure, possédant deux méthodes et aucun attribut : affichageDesc(t);
affichageDesc(c);
return 0;
o affiche(), méthode publique, constante, virtuelle pure, et ne prenant aucun }
argument.
o une méthode Figure* copie() const, également virtuelle pure, chargée de faire une copie en mémoire de
l'objet et de retourner le pointeur sur cette copie
Trois sous-classes (héritage publique) de Figure : Cercle, Carre et Triangle.
Une classe nommée Dessin, qui modélise une collection de figures. Il s'agira d'une collection «hétérogène»
d'éléments créés dynamiquement par une méthode ad-hoc définie plus bas (ajouteFigure).
Pour chacune des classes Cercle, Carre et Triangle, définissez les attributs (privés/protégés) requis pour
modéliser les objets correspondants.
Définissez également, pour chacune de ces sous-classes, un constructeur pouvant être utilisé comme constructeur
par défaut, un constructeur de copie et un destructeur.
Dans les trois cas, affichez un message indiquant le type de l'objet et la nature du constructeur/destructeur.
Définissez la méthode de copie en utilisant le constructeur de copie.
Finalement, définissez la méthode virtuelle affiche, affichant le type de l'instance et la valeur de ses attributs.
Ajoutez un destructeur explicite pour la classe Dessin, et définissez-le de sorte qu'il détruise les figures
stockées dans la collection en libérant leur espace mémoire (puisque c'est cette classeDessin qui, dans sa
méthode ajouteFigure, alloue cette mémoire).
Comme pour les autres destructeurs, affichez en début de bloc un message, afin de permettre le suivi du
déroulement des opérations.
Prototypez et définissez ensuite les méthodes suivantes à la classe Dessin :
qui ajoute à la collection une copie de la figure donnée en paramètre en faisant appel à sa méthode copie.
void affiche() constqui affiche tous les éléments de la collection. int main() {
Testez votre programme avec le main suivant:
Dessin dessin;
dessin.ajouteFigure(Triangle(3,4));
Compilez en ignorant pour l'instant les warnings. Testez le programme et dessin.ajouteFigure(Carre(2));
assurez vous de bien comprendre l'origine de tous les messages affichés. dessin.ajouteFigure(Triangle(6,1));
dessin.ajouteFigure(Cercle(12));
Si vous avez suivi les directives de l'énoncé, votre programme devrait, en
dernier lieu, indiquer que le destructeur du dessin est invoqué... mais pas les
cout << endl << "Affichage du dessin : "
destructeurs des figures stockées dans le dessin !
Pourquoi ?
<< endl;
Voyez-vous un moyen permettant de pallier cela ? dessin.affiche();
}
Corrigez (en ajoutant 1 seul mot) votre programme.
Ajoutez la fonction suivante, juste avant votre main : void unCercleDePlus(Dessin const& img)
{
Dessin tmp(img);
Appelez cette fonction depuis main (n'importe où après la première tmp.ajouteFigure(Cercle(1));
instruction d'ajout de figure). cout << "Affichage de 'tmp': " << endl;
Si tout se passe « normalement », le système doit interrompre tmp.affiche();
prématurément votre programme, en vous adressant un message hargneux }
(genre «segmentation fault», ou «bus error»). (Il est cependant possible que
sur certains systèmes cette erreur, qui se produit pourtant effectivement, ne soit pas détectée et ne donne lieu à
aucun comportement visible.)
Quel est, à votre avis, le motif pour un tel comportement ? Corrigez votre programme.
1. Classe Forme
Définissez une classe Forme en la dotant d'une méthode qui affiche [...]
#include <iostream>
using namespace std;
class Forme {
public:
void description() const {
cout << "Ceci est une forme." << endl;
}
};
Ajoutez au programme une classe Cercle héritant de la classe Forme, et possédant une
méthode voiddescription() qui affiche [...]
class Cercle : public Forme {
public:
void description() const {
cout << "Ceci est un cercle." << endl;
}
};
La différence vient de ce que c.description(); appelle la méthode description() de la classe Cercle (c'est-à-
dire Cercle::description()), alors que f2.description(); appelle celle de la classe Forme (c'est-à-
dire Forme::description()), bien que elle ait été construite par une copie d'un cercle.
Le polymorphisme n'opère pas ici car aucune des deux conditions nécessaires n'est remplie : la méthode n'est pas
virtuelle et on ne passe par pas par des références ni pointeurs.
Avec cette fonction, nous apportons une solution au second aspect puisqu'en effet nous passons l'argument par
référence.
Le résultat n'est cependant toujours pas satisfaisant (c'est toujours la méthode Forme::description() qui est appelée)
car le premier problème subsiste : la méthode n'est pas virtuelle.
Modifiez le programme (ajoutez 1 seul mot) pour que le résultat soit plus conforme à ce que l'on pourrait attendre.
Il suffit donc d'ajouter virtual devant le prototype de la méthode description de la classe Forme.
class Forme {
public:
virtual void description() const {
cout << "Ceci est une forme." << endl;
}
};
int main()
{
Cercle c;
affichageDesc(c);
return 0;
}
2. Formes abstraites
Modifiez la classe Forme de manière à en faire une classe abstraite [...]
class Forme {
public:
virtual void description() const {
cout << "Ceci est une forme !" << endl;
}
virtual double aire() const = 0;
};
Ce qui en fait une méthode virtuelle pure c'est le =0 derrière qui indique que pour cette classe cette méthode ne sera
pas implémentée (i.e. pas de définition, c.-à-d. pas de corps).
Écrivez une classe Triangle et modifiez la classe Cercle existante héritant toutes deux de la classe Forme, et
implémentant les méthodes aire() et description(). [...]
class Cercle : public Forme {
public:
Cercle(double r = 0.0) : rayon(r) {}
void description() const {
cout << "Ceci est un cercle !" << endl;
}
double aire() const { return M_PI * rayon * rayon; }
private:
double rayon;
};
class Forme {
public:
virtual void description() const {
cout << "Ceci est une forme !" << endl;
}
virtual double aire() const = 0;
};
void affichageDesc(Forme& f) {
f.description();
cout << " son aire est " << f.aire() << endl;
}
int main()
{
Cercle c(5);
Triangle t(10, 2);
affichageDesc(t);
affichageDesc(c);
return 0;
}
3. Classe Figure
Prototypez et définissez les classes [...] Figure
#include <iostream>
using namespace std;
class Figure {
public:
virtual void affiche () const = 0;
virtual Figure* copie() const = 0;
};
};
Une classe nommée Dessin, qui modélise une collection de figures. [...]
class Dessin {
public:
void ajouteFigure();
private:
vector<Figure*> contenu;
};
Comme conseillé en cours, plutôt que d'hériter de la classe vector, on préfère encapsuler la collection dans la classe.
Remarque :Pour les pointeurs, nous avons ici choisi des pointeurs «à la C». En C++11, on pourrait plutôt opter pour
des unique_prt. Une solution dans ce sens est proposée à la fin.
[...] 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 r = 0.0) : rayon(r) {
cout << "Construction d'un cercle de rayon " << rayon << endl;
}
Cercle(const Cercle& autre) : Figure(autre), rayon(autre.rayon) {
cout << "Copie d'un cercle de rayon " << rayon << endl;
}
~Cercle() { cout << "Destruction d'un cercle" << endl; }
private:
double rayon;
};
//----------------------------------------------------------------------
class Carre : public Figure {
public:
Carre(double x = 0.0) : cote(x) {
cout << "Construction d'un carré de coté " << cote << endl;
}
private:
double cote;
};
//----------------------------------------------------------------------
class Triangle : public Figure {
public:
Triangle(double b = 0.0, double h = 0.0) : base(b), hauteur(h) {
cout << "Construction d'un triangle " << base << "x" << hauteur << endl;
}
Triangle(const Triangle& autre) : Figure(autre)
, base(autre.base), hauteur(autre.hauteur) {
cout << "Copie d'un triangle " << base << "x" << hauteur << endl;
}
~Triangle() { cout << "Destruction d'un triangle" << endl; }
private:
double base; double hauteur;
};
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;
}
...
};
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.
//----------------------------------------------------------------------
class Figure {
public:
virtual void affiche() const = 0;
virtual Figure* copie() const = 0;
virtual ~Figure() { cout << "Une figure de moins." << endl; }
};
//----------------------------------------------------------------------
class Cercle : public Figure {
public:
Cercle(double x = 0.0) : rayon(x) {
cout << "Construction d'un cercle de rayon " << rayon << endl;
}
private:
double rayon;
};
//----------------------------------------------------------------------
//----------------------------------------------------------------------
class Triangle : public Figure {
public:
Triangle(double b = 0.0, double h = 0.0) : base(b), hauteur(h) {
cout << "Construction d'un triangle " << base << "x" << hauteur << endl;
}
private:
double base; double hauteur;
};
//----------------------------------------------------------------------
class Dessin {
public:
~Dessin() {
cout << "Le dessins s'efface..." << endl;
for (unsigned int i(0); i < contenu.size(); ++i) delete contenu[i];
}
void ajouteFigure(const Figure& fig) {
contenu.push_back(fig.copie());
}
void affiche() const {
cout << "Je contiens :" << endl;
for (unsigned int i(0); i < contenu.size(); ++i) {
contenu[i]->affiche();
}
}
private:
vector<Figure*> contenu;
};
int main()
{
Dessin dessin;
dessin.ajouteFigure(Triangle(3,4));
dessin.ajouteFigure(Carre(2));
dessin.ajouteFigure(Triangle(6,1));
dessin.ajouteFigure(Cercle(12));
unCercleDePlus(dessin); // impossible
[...] 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.
Pour y palier, il faudrait définir le constructeur de copie de Dessin afin de faire une copie profonde.
class Figure {
public:
virtual void affiche() const = 0;
virtual unique_ptr<Figure> copie() const = 0;
virtual ~Figure() { cout << "Une figure de moins." << endl; }
};
class Dessin {
public:
~Dessin() { cout << "Le dessins s'efface..." << endl; }
void ajouteFigure(const Figure& fig) { contenu.push_back(fig.copie()); }
void affiche() const {
cout << "Je contiens :" << endl;
for (auto const& fig : contenu) { fig->affiche(); }
}
private:
vector<unique_ptr<Figure>> contenu;
};
int main()
{
Dessin dessin;
dessin.ajouteFigure(Triangle(3,4));
dessin.ajouteFigure(Carre(2));
dessin.ajouteFigure(Triangle(6,1));
dessin.ajouteFigure(Cercle(12));