Exercice 1 | Exercice 2 |
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; };
[...] 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 { 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.
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) { } Carre(const Carre& autre) : Figure(autre), cote(autre.cote) { } 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; };
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 :
Retourner le pointeur sur une copie de l'objet :
Figure* copie() const { }(jusque là c'était déjà donné dans l'énoncé au niveau de
Figure
).
En utilisant le constructeur de copie qu'il dit le Monsieur...
Par exemple pour la classe Cercle
, cela
s'écrit Cercle(...)
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 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 { } ... }; 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 (auto & fig : contenu) delete fig; }
Prototypez et définissez ensuite
les méthodes suivantes à la classe 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; };
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 le code complet :
#include <iostream> #include <vector> using namespace std; //---------------------------------------------------------------------- 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; } 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; } Figure* copie() const { return new Cercle(*this); } void affiche() const { cout << "Un cercle de rayon " << rayon << endl; } private: double rayon; }; //---------------------------------------------------------------------- class Carre : public Figure { public: Carre(double a = 0.0) : cote(a) { } Carre(const Carre& autre) : Figure(autre), cote(autre.cote) { } Figure* copie() const { return new Carre(*this); } void affiche() const { } 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; } Figure* copie() const { return new Triangle(*this); } void affiche() const { cout << "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; }; void unCercleDePlus(const Dessin& img) { Dessin tmp(img); tmp.ajouteFigure(Cercle(1)); cout << "Affichage de 'tmp': " << endl; tmp.affiche(); } 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 cout << endl << "Affichage du dessin : " << endl; dessin.affiche(); return 0; }
[...]
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.
Voici enfin une version en C++11 avec unique_prt
(pour varier).
Notez comme l'emploi des unique_prt
résoud de façon drastique le problème précédent : on ne peut simplement plus faire de copie de Dessin
.
#include <iostream> #include <vector> #include <memory> using namespace std; class Figure { public: virtual void affiche() const = 0; virtual unique_ptr<Figure> copie() const = 0; virtual ~Figure() { cout << "Une figure de moins." << endl; } }; class Cercle : public Figure { public: Cercle(double r = 0.0) : rayon(r) { cout << "Et hop, un cercle de plus !" << endl; } Cercle(const Cercle& autre) : rayon(autre.rayon) { cout << "Et encore un cercle qui fait des petits !" << endl; } ~Cercle() { cout << "le dernier cercle ?" << endl; } unique_ptr<Figure> copie() const override { return unique_ptr<Figure>(new Cercle(*this)); } void affiche() const override { cout << "Un cercle de rayon " << rayon << endl; } private: double rayon; }; class Carre : public Figure { public: Carre(double a = 0.0) : cote(a) { } Carre(const Carre& autre) : cote(autre.cote) { } unique_ptr<Figure> copie() const override { return unique_ptr<Figure>(new Carre(*this)); } void affiche() const override { } private: double cote; }; class Triangle : public Figure { public: Triangle(double h = 0.0, double b = 0.0) : base(b), hauteur(h) { } Triangle(const Triangle& autre) : base(autre.base), hauteur(autre.hauteur) { cout << "Et encore un triangle qui fait des petits !" << endl; } ~Triangle() { cout << "La fin du triangle." << endl; } unique_ptr<Figure> copie() const override { return unique_ptr<Figure>(new Triangle(*this)); } void affiche() const override { cout << "Un triangle " << base << "x" << hauteur << endl; } private: double base; double hauteur; }; 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)); cout << endl << "Affichage du dessin : " << endl; dessin.affiche(); return 0; }
#include <iostream> #include <string> #include <vector> using namespace std; /* La classe Livre */ class Livre { public: Livre(string, string, int, bool); virtual ~Livre() {} virtual double calculer_prix() const ; virtual void afficher() const ; protected: string titre ; string auteur ; int nbPages ; bool bestseller ; }; // constructeur Livre::Livre(string titre, string auteur, int nbPages, bool bestseller) : titre(titre), auteur(auteur), nbPages(nbPages), bestseller(bestseller) {} // calcul du prix d'un livre double Livre::calculer_prix() const { double p(nbPages* 0.3); if (bestseller) return (p + 50.0); else return p; } // affichage des caracteristiques d'un livre void Livre::afficher() const { cout << "titre : " << titre <<endl; cout << "auteur : " << auteur <<endl; cout << "nombre de pages : " << nbPages <<endl; cout << "bestseller : " ; if (bestseller) cout << "oui"; else cout << "non"; cout << endl; cout << "prix : " << calculer_prix() << endl; } /* la sous-classe Roman*/ class Roman: public Livre { public: Roman(string, string, int, bool, bool); virtual ~Roman() {} virtual void afficher() const ; protected: bool biographie; }; Roman::Roman(string titre, string auteur, int nbPages, bool bestseller, bool biographie) : Livre(titre, auteur, nbPages, bestseller), biographie(biographie) {} void Roman::afficher() const { Livre::afficher(); if (biographie) cout << "Ce roman est une biographie" << endl; else cout << "Ce roman n'est pas une biographie" << endl; } /* les romans policiers */ class Policier : public Roman { public: Policier(string, string, int, bool, bool); virtual ~Policier() {} virtual double calculer_prix() const ; }; double Policier::calculer_prix() const { double p (Livre::calculer_prix() - 10.0); if (p <= 0.0) p = 1.0; return p; } /* la sous-classe BeauLivre */ class BeauLivre : public Livre { public: virtual ~BeauLivre() {} virtual double calculer_prix() const ; }; double BeauLivre::calculer_prix() const { return (Livre::calculer_prix() + 30.0); } /* la classe Librairie */ class Librairie { public: void ajouter_livre(Livre* livre) { contenu.push_back(livre); } void vider_stock(); void afficher() const; private: vector <Livre*> contenu; }; void Librairie::afficher() const { for (auto livre : contenu) { livre->afficher(); cout << endl; } } void Librairie::vider_stock() { for (auto livre : contenu) { delete livre; } contenu.clear(); } int main() { Librairie l; l.ajouter_livre(new Policier("Le chien des Baskerville", "A.C.Doyle", 221, false, false)); l.ajouter_livre(new Policier("Le Parrain ", "A.Cuso", 367, true, false)); l.ajouter_livre(new Roman("Le baron perche", "I. Calvino", 283, false, false)); l.ajouter_livre(new Roman ("Memoires de Geronimo", "S.M. Barrett", 173, false, true)); l.ajouter_livre(new BeauLivre ("Fleuves d'Europe", "C. Osborne", 150, false)); l.afficher(); l.vider_stock(); return 0; }