Choisissez votre style : colorisé, impression

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 :

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.


Dernière mise à jour : 2021/03/21 18:51:12 )