Normaliser une classe.

Forme de Coplien.

classe de Coplien

Voir version :

Pas de dépendances

Télécharger :

#include <iostream>
#include <string.h>

class Fstring
{
protected:
    char* p;
public:
    //Fstring();
    Fstring(char* inp);
    friend std::ostream& operator<<(std::ostream&,const Fstring&);
    //~Fstring();
    //Fstring(const Fstring&);
    //Fstring& operator=(const Fstring&);
};

/*Fstring::Fstring()
{
    p = new char[1];
    p[0]='\0';
    std::cout << "construction de base adresse " << (int)p << std::endl;
}*/

Fstring::Fstring(char* inp)
{
    p = new char[strlen(inp)+1];
    strcpy(p,inp);
    std::cout << "construction adresse " << (int)p << std::endl;
}

std::ostream& operator<<(std::ostream& os,const Fstring& s)
{
    os << s.p;
    return os;
}

/*Fstring::~Fstring()
{
    std::cout << "destruction adresse " << (int)p << std::endl;
    delete [] p;
}*/

/*Fstring::Fstring(const Fstring& in)
{
    p = new char[strlen(in.p)+1];
    strcpy(p,in.p);
    std::cout << "construction par recopie adresse " << (int)p << std::endl;
}

Fstring& Fstring::operator=(const Fstring& in)
{
    std::cout << "Assign egal "<< std::endl;    
    if (&in!=this)
    {
        std::cout << "destruction dans egal adresse " << (int)p << std::endl;        
        delete [] p;
        p = new char[strlen(in.p)+1];
        strcpy(p,in.p);
        std::cout << "construction dans egal adresse " << (int)p << std::endl;
    }
    return *this;
}
*/

int main()
{
    Fstring f("plouf");
    std::cout << f << std::endl;
    Fstring g(f);
    //Fstring h;
    //h = g;
    std::cout << f << std::endl;
    std::cout << g << std::endl;
    //std::cout << h << std::endl;
    //g = g;
    std::cout << "programme fini" << std::endl;
    return 0;
}




Commentaires


  Un tuto pour bien normaliser les classes. On parle de normalisation de Coplien.

  Exemple : une classe Fstring, qui contient un pointeur vers une chaine.
  

  On a :

  Un constructeur a partir d'un char*
  un operateur << pour afficher la chaine.

  Lancez le programme.
  un f est créé : le constructeur est appelé : un new est fait.

  puis on construit un deuxieme Fstring avec :

  Fstring g(f);  --> ça recopie

  Comme vous n'avez pas encore dit comment recopier, ça recopie octet par octet, donc ça recopie le pointeur, et non la zone allouée
  uniquement la valeur du pointeur.

  Voila, puis on sort du programme.

  --> Un new qui n'a pas été deleté : fuite mémoire, pas bon.

DESTRUCTEUR
***********

  Activez la ligne dans la classe
  //~Fstring();

  Et activez l'implémentation de cela plus bas.

  Un destructeur est unique, il ne prend pas de parametres, a le nom de la classe précédé de ~
  Dans le destructeur, on delete p. Normal.

  Le destructeur sera appelé automatiquement a la fin de la vie du Fstring. Ici, on a déclaré des Fstring dans le main
  Donc les destructeurs seront appelés tout seul a la fin du main...

  Relancez le programme.
  ça plante ? --> Non ? Vous avez de la "chance"
  Oui ? --> Normal.

  Regardez simplement, vous avez construit p une fois, et vous essayez de le détruire 2 fois. La premiere fois, ça passe,
  la deuxieme fois, c'est moins sur...

  Tout ça a cause de g : il a été créé en recopiant f, et a recopié la valeur du pointeur, sans rien allouer...

CONSTRUCTEUR PAR RECOPIE
************************

  Pour remédier à cela, activez la ligne :

  //Fstring(const Fstring&);
  ainsi que son implémentation plus bas.

  Cette fois, on dit explicitement comment on construit un Fstring a partir d'un autre.
  On refait un new et on recopie.

  Lancez le programme : cette fois, 2 allocations, 2 désallocations. Parfait !!

  Pourquoi passer par référence le Fstring ?
  --> Parce que sinon, vous le passez par copie. Et si vous le passez par copie, ça appelle le constructeur par recopie...
  Donc ça rappelle exactement la meme chose, qui lui meme va rappeler, etc etc.... ça boucle infiniment, et vous finissez par
  vous prendre un stack overflow (trop d'empilage d'appel de fonctions...)

CONSTRUCTEUR VIDE
*****************

  Nous décidons de créer un objet vide :

  activez : 
  //Fstring h;

  Lancez : il rale car il manque un constructeur par défaut.
  Qu'a cela ne tienne, activez l'implémentation du constructeur par défaut Fstring()

ASSIGNATION DU =
****************

  Activez la ligne :

  //h = g;

  Vous avez envie de recopier g dans h, mais savez vous faire = ?
  Si vous ne savez pas faire, par défaut, le compilo va recopier betement les valeurs, et donc, vous allez tomber dans le meme piege :
  2 new et 3 delete --> risque de plantage.

  Activez //Fstring& operator=(const Fstring&);
  et son implémentation.
  

  Dans le if, on voit le meme code que le constructeur par recopie, précédé d'un delete :
  en effet, on détruit l'objet déja existant (ici h) avant de lui recopier g.

  Mais pourquoi le if ?

  Je répondrai par une question :
  Que se passe t il si vous faites g = g ?

  avec le delete, vous détruisez g. Puis ensuite, vous essayez de copier le contenu de g dedans...
  ayayaye.... risque de plantage.

  La ligne 
  if (&in!=this)
  vous vérifiez si l'adresse de l'objet a copier n'est pas lui meme (this)
  en gros, vous détectez que vous n'avez pas la meme chose  a gauche et a droite...


CLASSE NORMALISEE
*****************

  A partir de la, votre classe est normalisée, et blindée, vous pouvez la manipuler comme un int, meme s'il y a des pointeurs dessous
  (si vous avez bien rempli les méthodes membres, tout est bon)

  Récapitulons :
  Si on veut une classe normalisée, il FAUT :

  - un constructeur vide
  - un destructeur
  - un constructeur par recopie
  - une surcharge de =.



PIEGES ET RALENTISSEMENTS
*************************

  Attention aux recopies abusives : si vous abusez, ou ne faites pas attention, vous allez recopier a tout va, et ça va ralentir :

  void fonction(Fstring s)  // appel ici du constructeur par recopie de Fstring.
  {


  }

  int main()
  {
	Fstring a;
	fonction(a);


  }


  Si vous regardez les lignes ci dessus : vous passez un Fstring dans "fonction"
  Par défaut, c'est une copie que vous passez....
  

  Si vous faites ça souvent et/ou que Fstring est longue a recopier, vous effondrez les performances.
  Des fois, c'est utile de recopier, des fois, c'est inutile, pensez bien a cela...

  Ici, il aurait fallu mettre :

  void fonction(Fstring& s)

  voir 

  void fonction(const Fstring s)

  si vous voulez vous assurer que s ne sera pas modifiée.... (voir tuto sur const plus loin)