[C++] pointeurs "intelligents" (désolé, c'est leur nom)

pointeurs "intelligents" (désolé, c'est leur nom) [C++] - C++ - Programmation

Marsh Posté le 12-12-2002 à 04:45:28    

J'ai horreur du C++ mais j'avais une interro sur cette bouse Lundi alors je me suis un peu entraîné.
Je suis parti d'un TD que j'ai eu récement http://cassoulet.univ-brest.fr/PEDA/C++/td7/HTML/  
 
N'oubliez pas que les smart-pointeurs sont très inefficaces ! Même s'il sont toujours mieux que new/delete/copie profonde. Ils sont complètements insufisants par rapport à un vrai GC !
 
Voici une petite implantation des pointeurs intelligents en C++.
Le principe est simple, chaque objet possède un compteur qui est incrémenté de 1 à chaque fois qu'on prend un pointeur dessus et décrémenté à chaque fois qu'on vire le pointeur de dessus. Quand le compteur vaut 0, on détruit l'objet.
 
On a une classe Pointeur<T> qui représentera le pointeur (qui en terme de mémoire coûtera autant qu'un pointeur normal). Une classe Conteneur<T> qui contiendra l'objet pointé (en expansé) et le compteur. Le conteneur pourrait ne garder qu'une référence mais ceci augmenterait l'indirection de 1 cran, en ayant une création plus rapide, c'est un compromis.  
 
La classe conteneur permet aussi de pallier une des nombreuses faiblaisses du C++, on pourrait mettre bind() et unbind() directement dans les objets pointés, introduisant une contrainte dans la généricité, ce qu'est incapable d'exprimer C++, donc un code obscur et des messages d'erreur bizarres pour les gens qui le savent pas (voir les solutions de O'caml et Eiffel). En ayant une classe codée en dur, on connecte les interfaces proprement.
 
Le principe : un pointeur est pris sur un objet à l'instanciation du Pointeur si on lui passe, ou lors de l'affectation par = qui sera donc surchargé.
Un pointeur est relaché à la destruction de celui-ci ou lors de l'affectation encore.
L'affectation diminue de 1 le compteur de l'ancien pointé et augmente de 1 celui du nouveau pointé.
->  et *  sont surchargés pour masquer le pointeur et le conteneur.
==  et !=  sont surchargés pour garder leur sémantique d'identité.
 
Je n'utilise pas "friend" car il n'est pas question que quelqu'un hors du champ d'une classe touche aux champs privés (voir la clause export de Eiffel pour l'exportation sélective des caratéristiques). C'est pourquoi le constructeur de RendezVous (create()) est public et non privé avec Agenda comme ami.  
J'ai viré les fonctions qui servaient uniquement à répondre à l'énoncé du TD.
L'implantation de l'agenda est une liste car seul list, vector et has_map sont au programme et que j'avais pas envie de lire la doc de la STL.
 
 

Code :
  1. /*agenda.C*/
  2. /* un petit exemple d'agenda avec des rendez-vous gérés par smart-pointeurs*/
  3. using namespace std;
  4. #include <string.h>
  5. #include <stdlib.h>
  6. #include <list>
  7. #include <string>
  8. #include <iostream>
  9. class RendezVous;                     // le rendez-vous
  10. class Carnet;                         // le carnet de rendez-vous
  11. template<typename T> class Pointeur;  // le pointeur "intelligent" (si tant est que cette technique est intelligente)
  12. template<typename T> class Conteneur; // le conteneur, contiendra l'instance et son compteur
  13. //on wrappe un peu les notations lourdes
  14. typedef Pointeur<const string> ConstStrPtr;
  15. ostream & operator << (ostream &o, const RendezVous &f);
  16. ostream & operator << (ostream &o, const Carnet &c);
  17. template<typename T> class Conteneur
  18. {
  19. private :
  20.   T real; //l'instance, sans indirection
  21.   unsigned int refcount; //le compteur de références
  22. public :
  23.   Conteneur():refcount(0) {}
  24.   Conteneur(const T &r):real(r), refcount(0) {}
  25.   Conteneur(Conteneur<T> &c):real(c.real),refcount(0){}
  26.   ~Conteneur(){
  27.     cout << "DESTRUCTION DE :" << endl;
  28.     cout << real << endl;
  29.     cout << "destruction du conteneur" << endl;
  30.     cout << "**********" << endl << endl;
  31.   }
  32.   void unbind() { // un pointeur s'en va
  33.     refcount--;
  34.     if (refcount == 0)
  35.       delete this;
  36.   }
  37.   void bind() { // un nouveau pointeur dessus
  38.     refcount++;
  39.   }
  40.   T* operator & () { // on planque le conteneur
  41.     return
  42.   } 
  43. };
  44. template<typename T> class Pointeur
  45. {
  46. private :
  47.   Conteneur<T> *real;
  48. public :
  49.   Pointeur():real(0) {}
  50.   Pointeur(T p) :real(new Conteneur<T>(p)) {real->bind();}
  51.   Pointeur(Conteneur<T> *p) :real(p) {p->bind();}
  52.   Pointeur(const Pointeur &r) :real(r.real) {if (r.real != 0) r.real->bind();}
  53.   ~Pointeur() { // quand on meurt, on décrémente
  54.     if (real != 0)
  55.       real->unbind();
  56.   }
  57.   Pointeur operator = (Pointeur r) { // ici est toute la feinte
  58.     if (r.real != real) {
  59.       if (real)
  60.         real->unbind();
  61.       if (r.real)
  62.         r.real->bind();
  63.       real = r.real;
  64.     }
  65.     return r;
  66.   }
  67.   T * operator -> (void) const { // on cache le système smart-pointeur/conteneur
  68.     return &(*real);
  69.   }
  70.   T & operator * (void) const { // pareil, sauf qu'on renvoie l'instance
  71.     return *(&(*real)); //oui, c'est comique
  72.   }
  73.   bool operator == (const Pointeur &p) const { // égalité de pointeur
  74.     return real == p.real;
  75.   }
  76.   bool operator != (const Pointeur &p) const { // différence de pointeurs
  77.     return real != p.real;
  78.   }
  79.   static Pointeur<T> wrappe_et_pointe(T &c) {
  80.     return Pointeur(new Conteneur<T>(T(c)));
  81.   }
  82. };
  83. class RendezVous
  84. {
  85. private:
  86.   ConstStrPtr nom;
  87.   ConstStrPtr num_tel;
  88.   ConstStrPtr lieu;
  89.   ConstStrPtr date;
  90.  
  91. public:
  92.   RendezVous(const RendezVous &r):nom(r.nom), num_tel(r.num_tel), lieu(r.lieu), date(r.date){};
  93.   RendezVous(ConstStrPtr _nom, ConstStrPtr _num_tel, ConstStrPtr _lieu, ConstStrPtr _date)
  94.     :nom(_nom), num_tel(_num_tel), lieu(_lieu), date(_date){};
  95.   ostream & printOn(ostream & o) const {
  96.     o << "nom : "  << *nom     << endl;
  97.     o << "tel : "  << *num_tel << endl;
  98.     o << "lieu : " << *lieu    << endl;
  99.     o << "date : " << *date;
  100.     return o;
  101.   }
  102.   ConstStrPtr get_date(void) const {
  103.     return date;
  104.   }
  105. };
  106. class Carnet
  107. {
  108. //on wrappe un peu les notations lourdes
  109. typedef Pointeur<const RendezVous> ConstRdvPtr;
  110. typedef list<ConstRdvPtr > ListRdv;
  111. typedef ListRdv::const_iterator ConstIterList;
  112. typedef ListRdv::iterator IterList;
  113. private:
  114.   ListRdv rdvs; // la collection de rendez-vous
  115.  
  116.   void insert(ConstRdvPtr r) {
  117.     rdvs.push_back(r);
  118.   }
  119.  
  120. public :
  121.   Carnet(void) {};
  122.   void add(ConstStrPtr nom, ConstStrPtr num_tel, ConstStrPtr lieu, ConstStrPtr date) {
  123.     ConstRdvPtr p = ConstRdvPtr::wrappe_et_pointe(RendezVous(nom, num_tel, lieu,date));
  124.     insert(p);
  125.   }
  126. //recherche une date précise et insère tous ceux qui correspondent dans c
  127.   void cherche_date(Carnet &c, ConstStrPtr date) const {   
  128.     for (ConstIterList i = rdvs.begin(); i != rdvs.end(); ++i)
  129.       if (!(*i)->get_date()->compare(*date))
  130. c.insert(*i);
  131.   }
  132. // supprime de l'instance courante tous les rendez-vous contenus dans c
  133. //passe silencieusement s'il ne trouve pas
  134.   void supprimer(const Carnet &c) {
  135.     for(ConstIterList j = c.rdvs.begin(); j != c.rdvs.end(); ++j) {
  136.       ConstRdvPtr toDie = *j;
  137.       for(IterList i = rdvs.begin(); i != rdvs.end(); ++i)
  138. if (*i == toDie) {
  139.   rdvs.erase(i);
  140.   break;
  141. }
  142.     }
  143.   }
  144. //ceux qui font du smalltalk savent :-)
  145.   ostream &printOn(ostream & o) const {
  146.     o << "--- carnet ---" << endl;
  147.     for (ConstIterList i = rdvs.begin(); i != rdvs.end(); ++i)
  148.       o << **i << endl << endl;
  149.     o << "--------------" << endl;
  150.     return o;
  151.   }
  152. };
  153. ostream & operator << (ostream & o, const RendezVous & f)
  154. {
  155.   return f.printOn(o);
  156. }
  157. ostream & operator << (ostream & o, const Carnet & c)
  158. {
  159.   return c.printOn(o);
  160. }
  161. //un peu d'utilitaire
  162. ConstStrPtr ps_from_c(char *s)
  163. {
  164.   return ConstStrPtr::wrappe_et_pointe(string(s));
  165. }
  166. int main()
  167. {
  168.   Carnet carnet;
  169.   //quelques pointeurs partagés pour tromper l'ennemi
  170.   ConstStrPtr dsc = ps_from_c("DSC" );
  171.   ConstStrPtr onze_dec = ps_from_c("11/12/2002" );
  172.   ConstStrPtr tel = ps_from_c("02 97 15 40 56" );
  173.   carnet.add(ps_from_c("Roger" )  , tel, ps_from_c("DTC" ), ps_from_c("11/12/2012" ));
  174.   carnet.add(ps_from_c("Robert1" ), tel, dsc, onze_dec);
  175.   carnet.add(ps_from_c("Robert2" ), tel, dsc, onze_dec);
  176.   carnet.add(ps_from_c("Robert3" ), tel, dsc, ps_from_c("11/12/2052" ));
  177.   carnet.add(ps_from_c("Robert4" ), tel, dsc, onze_dec);
  178.   cout << "contenu du carnet : " << endl;
  179.   cout << carnet;
  180.   { // un petit bloc pour montrer les variables automatiques
  181.     Carnet c2;
  182.     cout << "recherche par fonction custom de la date 11/12/2002" << endl;
  183.     carnet.cherche_date(c2, *onze_dec);
  184.     cout << c2 << endl;
  185.     cout << "tentative de suppression des rendez-vous du 11/12/2002" << endl;
  186.     carnet.supprimer(c2);
  187.     cout << "sortie du bloc" << endl;   
  188.   } // à la sortie, c2 est détruit, les rendez-vous de c2 ne sont plus dans carnet, ils sont détruits automatiquement
  189.   cout << "on est hors du bloc" << endl;
  190.   cout << "restent :" << endl;
  191.   cout << carnet;
  192.   cout << "sortie du programme" << endl;
  193.   return 0;
  194. }


 
ça compile sans warnig avec gcc 3.2 -Wall -W.
 
Si vous avez des remarques, des méthodes plus fines pour certains trucs, j'attend.


Message édité par nraynaud le 12-12-2002 à 04:56:01
Reply

Marsh Posté le 12-12-2002 à 04:45:28   

Reply

Marsh Posté le 12-12-2002 à 10:53:31    

Ca a l'air pas trop mal, mais j'espère pour toi que tu trouveras un boulot loin du C++. C'est pas bon de se forcer a faire des choses qu'on aime pas :D
 
Sinon, j'ai une remarque deja :
 

Code :
  1. Pointeur operator = (Pointeur r) {// ici est toute la feinte
  2.           if (r.real != real) {
  3.               if (real)
  4.                   real->unbind();
  5.               if (r.real)
  6.                   r.real->bind();
  7.               real = r.real;
  8.           }
  9.           return r;
  10.       }


 
C'est quoi la raison de ne pas utiliser des references ? Faire des tonnes de recopies ?
 
Autre remarque : tu peux critiquer le C++ pour ne pas avoir de restriction sur les templates ( ou du moins, de ne pas faire d'erreur avant l'édition des liens ), mais la méthode de remplacement choisie présente de nombreux avantages comme le fait qu'elle marche avec n'importe quel objet, même s'il n'a pas été prévu pour ça, et elle marche aussi avec les types de base sans faire aucun changement.
 
Et sinon, dernière remarque. C'est quoi la question au juste ? Ton problème quoi :)

Reply

Marsh Posté le 12-12-2002 à 11:48:11    

Kristoph a écrit :

Ca a l'air pas trop mal, mais j'espère pour toi que tu trouveras un boulot loin du C++. C'est pas bon de se forcer a faire des choses qu'on aime pas :D
 
Sinon, j'ai une remarque deja :
 

Code :
  1. Pointeur operator = (Pointeur r) {// ici est toute la feinte
  2.           if (r.real != real) {
  3.               if (real)
  4.                   real->unbind();
  5.               if (r.real)
  6.                   r.real->bind();
  7.               real = r.real;
  8.           }
  9.           return r;
  10.       }


 
C'est quoi la raison de ne pas utiliser des references ? Faire des tonnes de recopies ?
 
Autre remarque : tu peux critiquer le C++ pour ne pas avoir de restriction sur les templates ( ou du moins, de ne pas faire d'erreur avant l'édition des liens ), mais la méthode de remplacement choisie présente de nombreux avantages comme le fait qu'elle marche avec n'importe quel objet, même s'il n'a pas été prévu pour ça, et elle marche aussi avec les types de base sans faire aucun changement.
 
Et sinon, dernière remarque. C'est quoi la question au juste ? Ton problème quoi :)


 
On ne référence jamais un pointeur dans la le cas "normal", ça ajoute une indirection et ça ne fait pas gagner de mémoire. Ben là c'est pareil, chaque instance de Pointeur coûte .... un pointeur en mémoire :-).
 
"même s'il n'a pas été prévu pour ça"
 
faudra que tu m'explique comment ce truc :

Code :
  1. template<typename T> void f(T &i) {
  2.   i.methode_requise_mais_pas_documentee_que_le_message_derreur_va_te_faire_tout_bizarre();
  3. }


(fragment non vérifié)
pourrait fonctionner avec une classe T passée en argument qui ne serait pas prévue pour.
 
Va voir
http://smarteiffel.loria.fr/libraries/dictionary.html
pour un petit exemple de la notation Eiffel de ça.
Dans un dictionnaire, il faut pourvoir hacher la clef et c'est explicitement contraint dans le template.
 
S'il y a une contrainte, elle doit être visible.
 
Sinon, y'a pas de question précise, qu'en pensez-vous ? Y a-t'il une grosse connerie ? Avez-vous des remarques ?
Pour essayer de remonter un peu le niveau face à du "comment je peux overclocker mon PC en C++ ?".


Message édité par nraynaud le 12-12-2002 à 11:57:57
Reply

Marsh Posté le 12-12-2002 à 11:51:05    

nraynaud a écrit :


Pour essayer de remonter un peu le niveau face à du "comment je peux overclocker mon PC en C++ ?".


facile, suffit de faire des sujets : "comment je peux overclocker mon PC en Eiffel ?"
 
[:dehors2]

Reply

Marsh Posté le 12-12-2002 à 11:55:47    

désolé, mais j'aime pas du tout ce genre d'instruction "delete this;" t'as l'impression que ca marche jusqu'au jour ou boom!  
 
 :pfff:


---------------
du bon usage de rand [C] / [C++]
Reply

Marsh Posté le 12-12-2002 à 12:01:03    

Taz@PPC a écrit :

désolé, mais j'aime pas du tout ce genre d'instruction "delete this;" t'as l'impression que ca marche jusqu'au jour ou boom!  
 
 :pfff:  


 
Quel est le risque ?
Tu proposes quoi pour l'autodestruction ?
Appeller une méthode en dehors de la classe qui elle-même va apeller delete ? ça revient au même.
T'as des "best-practices" à proposer ?


Message édité par nraynaud le 13-12-2002 à 04:18:14
Reply

Marsh Posté le 12-12-2002 à 12:04:10    

rajoute une indirection sur Conteneur::real et delete real quand nécessaire.
 
en remplacer ton delete this par un appel explicite au destructeur est aussi dangereux. le problème, c'est que ton objet va etre detruit (enfin plus ou moins, ca dépend des implémentation) mais il se peut qu'il soit toujours référencé (sens large) par ailleurs. je n'ai pas d'exemple sous la main, mais j'ai déjà passé des heures a me prendre la tete a cause d'un this->~().
 
autre idée: n'appelle jamais le destructeur explicitement, si tu as absolument besoin d'executer le code du destructeur (pour un operator= par exemple), fait une fonction de nettoyage appelée par le destructeur que tu pourras aussi appelé surement. Tout ca pause notemment des problèmes avec l'héritage.
 
 
personnellement, je n'aime pas trop les GC en C++, je préfère gérer moi meme (avec un peu d'aide quand meme). la seule chose que je m'autorise, ce sont des pool  :D


Message édité par Taz@PPC le 12-12-2002 à 12:22:19

---------------
du bon usage de rand [C] / [C++]
Reply

Marsh Posté le 12-12-2002 à 12:10:07    

Taz@PPC a écrit :

rajoute une indirection sur Conteneur::real et delete real quand nécessaire


 
1) j'ai expliqué pourquoi il n'y a pas d'indirection
2) il faudra de toute façon appeller delete sur le conteneur
3) j'attend toujours une explication rationelle du problème posé par delete this, il t'a fait peur quand tu était petit ? ou tu as une réelle explication avec peut-être même un papier sérieux au bout ?


Message édité par nraynaud le 13-12-2002 à 04:17:34
Reply

Marsh Posté le 12-12-2002 à 12:17:25    

nraynaud a écrit :


 
1) j'ai expliqué pourquoi il n'y a pas d'indirection
2) il faudra de toute façon appeller delete sur le conteneur
3) j'attend toujours une explication rationelle du problème posé par delete this, il t'as fait peur quand tu était petit ? ou tu a une réelle explication avec peut-être même un papier sérieux au bout ?

fais ce qui te plait. mais pour moi l'auto-destruction, c'est ce tirer dans le pied


---------------
du bon usage de rand [C] / [C++]
Reply

Marsh Posté le 12-12-2002 à 13:22:47    

"J'ai horreur du C++ mais j'avais une interro sur cette bouse"
 
ca m'enerve ca ...


---------------
-( BlackGoddess )-
Reply

Marsh Posté le 12-12-2002 à 13:22:47   

Reply

Marsh Posté le 12-12-2002 à 13:43:13    

Taz@PPC a écrit :

fais ce qui te plait. mais pour moi l'auto-destruction, c'est ce tirer dans le pied


 
d'accord avec toi, ca m'est arrive de le faire, mais ca me mettais pas en confiance :D

Reply

Marsh Posté le 12-12-2002 à 13:43:35    

blackgoddess a écrit :

"J'ai horreur du C++ mais j'avais une interro sur cette bouse"
 
ca m'enerve ca ...

c plutot twa qui m'enerve  :D  :hello:


---------------
du bon usage de rand [C] / [C++]
Reply

Marsh Posté le 12-12-2002 à 13:46:34    

les méthodes sont partie de la classe, pas de l'objet, donc je vois pas en quoi désallouer l'objet courant pourrait poser probleme :??:

Reply

Marsh Posté le 12-12-2002 à 13:49:44    

lorill a écrit :

les méthodes sont partie de la classe, pas de l'objet, donc je vois pas en quoi désallouer l'objet courant pourrait poser probleme :??:

le suicide c'est mal. faites ce que vous voulez: c'est pas parce que le code de windows est bourré de delete this et de this->~() qu'il faut faire pareil. je prends note. on verra bien


Message édité par Taz@PPC le 12-12-2002 à 13:50:39

---------------
du bon usage de rand [C] / [C++]
Reply

Marsh Posté le 12-12-2002 à 13:52:45    

Taz@PPC a écrit :

le suicide c'est mal. faites ce que vous voulez: c'est pas parce que le code de windows est bourré de delete this et de this->~() qu'il faut faire pareil. je prends note. on verra bien


je fais pas de C++, donc je fais ni l'un ni l'autre... je me renseigne simplement [:spamafote]


Message édité par lorill le 12-12-2002 à 13:53:05
Reply

Marsh Posté le 12-12-2002 à 14:00:13    

lorill a écrit :

les méthodes sont partie de la classe, pas de l'objet, donc je vois pas en quoi désallouer l'objet courant pourrait poser probleme :??:


 
Surtout dans le cas où l'instance est wrappée ! il ne peut plus y avoir d'autre pointeurs dessus (hormis le cas vicieux &*ptr mais j'avais pas envie de me prendre la tête, je crois qu'on touche aux limites du langage)

Reply

Marsh Posté le 13-12-2002 à 01:21:26    

Reply

Marsh Posté le 13-12-2002 à 01:44:21    

pour le delete this,
faut faire gaffe à pas allouer ce genre d'objet sur la pile ou ne pas faire des compositions. C'est donc pas denué de risque qu'il vaudrait mieux eviter.

Reply

Marsh Posté le 13-12-2002 à 04:13:53    

wpk a écrit :

pour le delete this,
faut faire gaffe à pas allouer ce genre d'objet sur la pile ou ne pas faire des compositions. C'est donc pas denué de risque qu'il vaudrait mieux eviter.


 
C'est pour ça que les constructeurs de Conteneur devraient être accessibles uniquement depuis wrappe_et_pointe().
 
Ceci dit, je n'ai découvert le problème des variables automatique que quelques posts plus haut, je n'y avais pas pensé, je n'ai pas l'habitude des langages qui créent des objets sur la pile !
 
Coup de bol, mon code ne pouvait pas en faire, mais il n'est malheureusement pas verrouillé.
 
Dernière minute : je viens de découvrir qu'on peut faire des classes internes en C++.
 
Refactoring du code suite à cette découverte :
 
Il faut mettre Conteneur dans Pointeur et RendezVous dans Agenda. Ca va déjà résoudre ce qui me chagrinait le plus.
 
Je viens de découvrir qu'il m'arrivait ce que je dénonçait plus haut, dans le destructeur de Conteneur, l'appel o << real suppose qu'il existe une surcharge de << pour le type de real, ce qui n'est bien entendu pas documenté par la déclaration de Conteneur. D'autre part, je vois pourquoi ils ont botté en touche sur la spécification de contrainte : la surcharge rend cette spécification extrèmement compliquée. Je sens que Meyer avait raison, la surcharge c'est casse-couilles (cf. le message d'erreur de << quand il trouve pas celui adapté à votre objet).
 
Comme RendezVous est devenu inaccessible à l'extérieur de Pointeur, j'ai dégagé l'interface commune Printable et ajouté un << en conséquence, ainsi, le conteneur peut prendre n'importe quoi qui soit membre de droite de << (pour les types primitifs par ex.) ou (en fait "y compris" ) n'importe quoi qui implante Printable. Printable elle-même est friend de << car elle ne possède pas d'état interne, c'est une simple interface. J'espère que ça n'ouvre pas l'état interne de ses implémenteurs à << mais je suppose que Stroustrup est pas con à ce point.
 
Cette fois-ci, le "delete this" devrait ne plus faire peur à personne, il est (si je me plante pas) impossible de créer un Conteneur hors des conditions strictes de wrappe_et_pointe().
 
Bien entendu, le main() ne bouge pas, de même que la modification de Pointeur ne modifie pas Agenda. Principe ouvert-fermé oblige.
 

Code :
  1. /*agenda.C un petit exemple d'agenda avec des rendez-vous gérés par smart-pointeurs*/
  2. using namespace std;
  3. #include <string.h>
  4. #include <stdlib.h>
  5. #include <list>
  6. #include <string>
  7. #include <iostream>
  8. class Carnet;                         // le carnet de rendez-vous
  9. template<typename T> class Pointeur;  // le pointeur "intelligent" (si tant est que cette technique est intelligente)
  10. class Printable;
  11. //on wrappe un peu les notations lourdes
  12. typedef Pointeur<const string> ConstStrPtr;
  13. ostream & operator << (ostream &o, const Printable &p);
  14. class Printable {
  15.   friend ostream & operator << (ostream &o, const Printable &p);
  16. public :
  17.   virtual ostream & printOn(ostream & o) const = 0;
  18.   virtual ~Printable(){};
  19. };
  20. template<typename T> class Pointeur {
  21. // T doit être un membre de droite possible de <<
  22. // T de type Printable est suffisant.
  23. private:
  24.   class Conteneur {
  25.   private :
  26.     T real; //l'instance, sans indirection
  27.     unsigned int refcount; //le compteur de références
  28.   public :
  29.     Conteneur():refcount(0) {}
  30.     Conteneur(const T &r):real(r), refcount(0) {}
  31.     Conteneur(Conteneur &c):real(c.real),refcount(0){}
  32.     ~Conteneur(){
  33.       cout << "DESTRUCTION DE :" << endl;
  34.       cout << real << endl;
  35.       cout << "destruction du conteneur" << endl;
  36.       cout << "**********" << endl << endl;
  37.     }
  38.     void unbind() { // un pointeur s'en va
  39.       refcount--;
  40.       if (refcount == 0)
  41.         delete this;
  42.     }
  43.     void bind() { // un nouveau pointeur dessus
  44.       refcount++;
  45.     }
  46.     T* operator & () { // on planque le conteneur
  47.       return
  48.     } 
  49.   };
  50.   Conteneur *real;
  51.   Pointeur(Conteneur *p) :real(p) {p->bind();}
  52. public :
  53.   Pointeur():real(0) {}
  54.   Pointeur(T p) :real(new Conteneur(p)) {real->bind();}
  55.   Pointeur(const Pointeur &r) :real(r.real) {if (r.real != 0) r.real->bind();}
  56.   ~Pointeur() { // quand on meurt, on décrémente
  57.     if (real != 0)
  58.       real->unbind();
  59.   }
  60.   Pointeur operator = (Pointeur r) { // ici est toute la feinte
  61.     if (r.real != real) {
  62.       if (real)
  63.         real->unbind();
  64.       if (r.real)
  65.         r.real->bind();
  66.       real = r.real;
  67.     }
  68.     return r;
  69.   }
  70.   T * operator -> (void) const { // on cache le système smart-pointeur/conteneur
  71.     return &(*real);
  72.   }
  73.   T & operator * (void) const { // pareil, sauf qu'on renvoie l'instance
  74.     return *(&(*real)); //oui, c'est comique
  75.   }
  76.   bool operator == (const Pointeur &p) const { // égalité de pointeur
  77.     return real == p.real;
  78.   }
  79.   bool operator != (const Pointeur &p) const { // différence de pointeurs
  80.     return real != p.real;
  81.   }
  82.   static Pointeur<T> wrappe_et_pointe(T &c) {
  83.     return Pointeur(new Conteneur(T(c)));
  84.   }
  85. };
  86. class Carnet : public Printable {
  87. private:
  88.   class RendezVous : public Printable {
  89.   private:
  90.     ConstStrPtr nom;
  91.     ConstStrPtr num_tel;
  92.     ConstStrPtr lieu;
  93.     ConstStrPtr date;
  94.  
  95.   public:
  96.     RendezVous(const RendezVous &r):Printable(), nom(r.nom), num_tel(r.num_tel), lieu(r.lieu), date(r.date){};
  97.     RendezVous(ConstStrPtr _nom, ConstStrPtr _num_tel, ConstStrPtr _lieu, ConstStrPtr _date)
  98.       :nom(_nom), num_tel(_num_tel), lieu(_lieu), date(_date){};
  99.     ostream & printOn(ostream & o) const {
  100.       o << "nom : "  << *nom     << endl;
  101.       o << "tel : "  << *num_tel << endl;
  102.       o << "lieu : " << *lieu    << endl;
  103.       o << "date : " << *date;
  104.       return o;
  105.     }
  106.     ConstStrPtr get_date(void) const {
  107.       return date;
  108.     }
  109.   };
  110.   //on wrappe un peu les notations lourdes
  111.   typedef Pointeur<const RendezVous> ConstRdvPtr;
  112.   typedef list<ConstRdvPtr > ListRdv;
  113.   typedef ListRdv::const_iterator ConstIterList;
  114.   typedef ListRdv::iterator IterList;
  115.   ListRdv rdvs; // la collection de rendez-vous
  116.  
  117.   void insert(ConstRdvPtr r) {
  118.     rdvs.push_back(r);
  119.   }
  120. public :
  121.   Carnet(void) {};
  122.   void add(ConstStrPtr nom, ConstStrPtr num_tel, ConstStrPtr lieu, ConstStrPtr date) {
  123.     ConstRdvPtr p = ConstRdvPtr::wrappe_et_pointe(RendezVous(nom, num_tel, lieu,date));
  124.     insert(p);
  125.   }
  126.   //recherche une date précise et insère tous ceux qui correspondent dans c
  127.   void cherche_date(Carnet &c, ConstStrPtr date) const {   
  128.     for (ConstIterList i = rdvs.begin(); i != rdvs.end(); ++i)
  129.       if (!(*i)->get_date()->compare(*date))
  130. c.insert(*i);
  131.   }
  132.   // supprime de l'instance courante tous les rendez-vous contenus dans c
  133.   //passe silencieusement s'il ne trouve pas
  134.   void supprimer(const Carnet &c) {
  135.     for(ConstIterList j = c.rdvs.begin(); j != c.rdvs.end(); ++j) {
  136.       ConstRdvPtr toDie = *j;
  137.       for(IterList i = rdvs.begin(); i != rdvs.end(); ++i)
  138. if (*i == toDie) {
  139.   rdvs.erase(i);
  140.   break;
  141. }
  142.     }
  143.   }
  144.   //ceux qui font du smalltalk savent :-)
  145.   ostream &printOn(ostream & o) const {
  146.     o << "--- carnet ---" << endl;
  147.     for (ConstIterList i = rdvs.begin(); i != rdvs.end(); ++i)
  148.       o<< (**i) << endl << endl;
  149.     o << "--------------" << endl;
  150.     return o;
  151.   }
  152. };
  153. ostream & operator << (ostream & o, const Printable &p) {
  154.   return p.printOn(o);
  155. }
  156. //un peu d'utilitaire
  157. ConstStrPtr ps_from_c(char *s) {
  158.   return ConstStrPtr::wrappe_et_pointe(string(s));
  159. }
  160. int main() {
  161.   Carnet carnet;
  162.   //quelques pointeurs partagés pour tromper l'ennemi
  163.   ConstStrPtr dsc = ps_from_c("DSC" );
  164.   ConstStrPtr onze_dec = ps_from_c("11/12/2002" );
  165.   ConstStrPtr tel = ps_from_c("02 97 15 40 56" );
  166.   carnet.add(ps_from_c("Roger" )  , tel, ps_from_c("DTC" ), ps_from_c("11/12/2012" ));
  167.   carnet.add(ps_from_c("Robert1" ), tel, dsc, onze_dec);
  168.   carnet.add(ps_from_c("Robert2" ), tel, dsc, onze_dec);
  169.   carnet.add(ps_from_c("Robert3" ), tel, dsc, ps_from_c("11/12/2052" ));
  170.   carnet.add(ps_from_c("Robert4" ), tel, dsc, onze_dec);
  171.   cout << "contenu du carnet : " << endl;
  172.   cout << carnet;
  173.   { // un petit bloc pour montrer les variables automatiques
  174.     Carnet c2;
  175.     cout << "recherche par fonction custom de la date 11/12/2002" << endl;
  176.     carnet.cherche_date(c2, *onze_dec);
  177.     cout << c2 << endl;
  178.     cout << "tentative de suppression des rendez-vous du 11/12/2002" << endl;
  179.     carnet.supprimer(c2);
  180.     cout << "sortie du bloc" << endl;   
  181.   } // à la sortie, c2 est détruit, les rendez-vous de c2 ne sont plus dans carnet, ils sont détruits automatiquement
  182.   cout << "on est hors du bloc" << endl;
  183.   cout << "restent :" << endl;
  184.   cout << carnet;
  185.   cout << "sortie du programme" << endl;
  186.   return 0;
  187. }


 
merci pour ces commentaires, j'espère que vous en avez d'autres ... y'a sûrement encore moyen de faire mieux.

Reply

Marsh Posté le 13-12-2002 à 09:40:09    

Encore une fois, je te suggere de passer tes paramètres par reference dans l'opérateur =. Si tu ne le fais pas, le C++ vas t'ajouter implicitement 2 opérateurs de recopie, un pour le passage de paramètre, et 1 de plus pour le résultat de la fonction avec crèation d'objets temporaires et appels à bind()/unbind() associés.

Reply

Marsh Posté le 13-12-2002 à 11:46:27    

Kristoph a écrit :

Encore une fois, je te suggere de passer tes paramètres par reference dans l'opérateur =. Si tu ne le fais pas, le C++ vas t'ajouter implicitement 2 opérateurs de recopie, un pour le passage de paramètre, et 1 de plus pour le résultat de la fonction avec crèation d'objets temporaires et appels à bind()/unbind() associés.


 
Je le sais, je l'ai fait exprès.  
 
Ceci n'est qu'un exercice de style et le but est d'utiliser Pointeur comme on l'aurait fait d'un truc avec une étoile devant.
Le vrai problème vient non pas de la recopie (qui ne coûte que 32 bits, je le rappelle) mais de l'appel à bind() et unbind(). Le problème vient du système de comptage de références qui est une grosse merde (je croyais avoir été clair dès le début, http://www.memorymanagement.org/faq.html#gc.ref ), pas de la copie.  
 
_La_ solution est d'utiliser un GC moderne, pas de bidouiller des références sur des pointeurs.

Reply

Marsh Posté le 13-12-2002 à 13:36:39    

Ca n'est pas une raison pour écrire du mauvais C++. Faire un opérateur de recopie qui n'utilise pas les references c'est mal. De plus, avec ton sysème, tu crée inutilement des variable locales implicites qui vont garder des références vers tes objets et ne seront netoyées on ne sait pas trop quand, mais très tard en général. Ceci peut affaiblire considérablement l'efficacité de ton système dans certains cas, et un GC à la place de tes smart_pointers ne résolverais pas ce problème de références superflues.

Reply

Marsh Posté le 13-12-2002 à 14:10:17    

Kristoph a écrit :

Ca n'est pas une raison pour écrire du mauvais C++. Faire un opérateur de recopie qui n'utilise pas les references c'est mal.  


 
Prendre une référence non instrumentée sur un pointeur dans un système instrumenté, c'est mal ! on fait quoi maintenant ? On boxe les pointeurs ?
 

Kristoph a écrit :


De plus, avec ton sysème, tu crée inutilement des variable locales implicites qui vont garder des références vers tes objets et ne seront netoyées on ne sait pas trop quand, mais très tard en général.


 
Je vois pas pourquoi une variable survivrait à son scope, en particulier si elle est crée sur la pile, à la sortie du scope est va dégager en même temps que la frame. Donc au plus tard elle dégage à la sortie du scope.
 

Kristoph a écrit :


 Ceci peut affaiblire considérablement l'efficacité de ton système dans certains cas, et un GC à la place de tes smart_pointers ne résolverais pas ce problème de références superflues.


 
C'est un système de merde de toute façon et je ne vois pas en quoi ça l'affaiblit.  
 
Ton problème de références, je le nie.
Un GC éviterait le surcoût dû à la mise à jour des compteurs de références et il évite d'exploser la localité spaciale.
 


Message édité par nraynaud le 13-12-2002 à 14:12:52
Reply

Marsh Posté le 13-12-2002 à 17:13:41    

Kristoph a écrit :

Ca n'est pas une raison pour écrire du mauvais C++. Faire un opérateur de recopie qui n'utilise pas les references c'est mal.


 
personnellement je définis d'abord la fonction membre swap et l'operator= vient trivialement: son apramètre est une copie, il ne me reste plus qu'a swapper avec la copie.


---------------
du bon usage de rand [C] / [C++]
Reply

Marsh Posté le 13-12-2002 à 18:00:53    

Ca me rappelle le swap-trick qui sert avec les vector et string pour réduire leur capacité le plus possible :

Code :
  1. string(s).swap(s);


Reply

Marsh Posté le 14-12-2002 à 02:46:16    

Mais... il dit du mal de C++ :??:
 
Avec des arguments pertinents en plus :fou:
 
C++ n'a pas le modèle objet rêvé, c'est clair.
Bah, je dirais simplement que le C++ est très conditionné par l'implémentation sous-jacente.
 
Quand aux spécifications de contraintes, j'ai peur que ce soit mal utilisé, et que ça souffre des mêmes problèmes que les spécifications d'exceptions:
http://www.gotw.ca/publications/mill22.htm A Pragmatic Look at Exception Specifications
En clair: beaucoup trop ardu à synchroniser pour être correctement fait par le développeur.
J'ai l'impression qu'il vaut mieux s'en remettre au compilateur pour découvrir les requis et non-requis.
 
Et pour l'ajout d'un ramasse miette (question de temps), j'ai lu plusieurs avis comme quoi il y aurait alors 2 façons d'écrire du C++.
 
 
J'aurais des opinions plus sûres quand j'aurais plus d'expérience...


Message édité par Musaran le 14-12-2002 à 02:48:24

---------------
Bricocheap: Montage de ventilo sur paté de mastic silicone
Reply

Marsh Posté le 14-12-2002 à 05:30:37    

Musaran a écrit :

Mais... il dit du mal de C++ :??:
 
Avec des arguments pertinents en plus :fou:
 
C++ n'a pas le modèle objet rêvé, c'est clair.
Bah, je dirais simplement que le C++ est très conditionné par l'implémentation sous-jacente.
 
Quand aux spécifications de contraintes, j'ai peur que ce soit mal utilisé, et que ça souffre des mêmes problèmes que les spécifications d'exceptions:
http://www.gotw.ca/publications/mill22.htm A Pragmatic Look at Exception Specifications
En clair: beaucoup trop ardu à synchroniser pour être correctement fait par le développeur.
J'ai l'impression qu'il vaut mieux s'en remettre au compilateur pour découvrir les requis et non-requis.
 
Et pour l'ajout d'un ramasse miette (question de temps), j'ai lu plusieurs avis comme quoi il y aurait alors 2 façons d'écrire du C++.
 
 
J'aurais des opinions plus sûres quand j'aurais plus d'expérience...


 
Quand j'ai passé RendezVous en interne à Carnet, j'ai viré la surcharge de << qui allait avec et j'ai tenté de compiler (j'avais oublié le << real dans le destructeur de Conteneur) bah heureusement que je connaissais le risque car aucune déclaration ne relie explicitement Carnet::Pointeur<const RendezVous> et <<.
 
Dans un truc où tu n'a pas accès au source, ça doit être encore pire. Hors le mec qui est sensé avoir spécifié son interface et ses contrats avant d'écrire sa classe, il est le seul à le savoir, et ce, depuis la spécification de sa classe.
 
ADA, Eiffel et O'caml (au moins, bien sûr) proposent la spécification de contrainte sur la généricité (O'caml le fait aussi en inférence mais c'est pas beau à voir !) je vois pas pourquoi C++ ne pourrait pas. D'autre part, ne pas l'avoir signifie utiliser un objet sans que son interface ne soit précisée nulle part (au moment de l'écriture du template).
 
 
Disez oui à la qualité ! empechez java de faire la même connerie.

Reply

Marsh Posté le 14-12-2002 à 12:16:29    

Citation :

ADA, Eiffel et O'caml (au moins, bien sûr) proposent la spécification de contrainte sur la généricité (O'caml le fait aussi en inférence mais c'est pas beau à voir !) je vois pas pourquoi C++ ne pourrait pas. D'autre part, ne pas l'avoir signifie utiliser un objet sans que son interface ne soit précisée nulle part (au moment de l'écriture du template).

 
 
je vois pas pourquoi on devrait forcement specifier une interface explicitement pour tout ce qui est template. C'est peut-etre un peu plus clair au moment de l'ecriture du code, mais comme cette notions est fortement subjective suivant la clarté du systeme de specification, autant laisser le compilateur faire son boulot de compilo ie te sortir des erreurs et warnings. Ce qui est vrai c'est que suivant le compilo, ces erreurs sont plus ou moins faciles à interpreter mais avec un peu de bouteille on s'y fait vite fait.

Reply

Marsh Posté le 14-12-2002 à 13:32:35    

wpk a écrit :

 
 
je vois pas pourquoi on devrait forcement specifier une interface explicitement pour tout ce qui est template.  


 
Pas forcément, uniquement si tu accède à l'objet dans ton template, Vector n'en a pas besoin, Hash_bidule en a besoin (il a besoin d'une interface de hachage). Tu contrains uniquement s'il y a lieu.

Reply

Marsh Posté le 14-12-2002 à 15:10:56    

nraynaud a écrit :


 
Pas forcément, uniquement si tu accède à l'objet dans ton template, Vector n'en a pas besoin, Hash_bidule en a besoin (il a besoin d'une interface de hachage). Tu contrains uniquement s'il y a lieu.


 
eh bien dans ce cas, rien de plus simple que d'aggreger dans ton objet non pas des types genriques mais des objets implementant une interface bien precise (une interface de hachage par exemple). Les templates ne sont pas la seule solution à la genericité, si tu veux imposer des contraintes fortes, tu peux le faire en utilisant le "coté objet" du C++ et non pas "le coté" template.

Reply

Marsh Posté le 14-12-2002 à 16:32:32    

wpk a écrit :


 
eh bien dans ce cas, rien de plus simple que d'aggreger dans ton objet non pas des types genriques mais des objets implementant une interface bien precise (une interface de hachage par exemple). Les templates ne sont pas la seule solution à la genericité, si tu veux imposer des contraintes fortes, tu peux le faire en utilisant le "coté objet" du C++ et non pas "le coté" template.


 
Bon, visiblement tu n'as pas une vision sereine de la théorie des types.
Tiens j'ai sorti une page que j'ai commencée sur un wiki privé :
http://nraynaud.com.free.fr/types.html
(pas la peine d'essayer l'éditer, c'est du "enregistrer sous" )
 
vers le bas, "Hiérarchie de types" -> "types génériques (2)" -> "contraintes sur les types génériques"  
 
J'espère avoir été clair, sur la différence entre généricité contrainte et typage direct par ce qui nous intéresse (composition mais aussi "mariage d'intérêt" au sens de Meyer).

Reply

Marsh Posté le    

Reply

Sujets relatifs:

Leave a Replay

Make sure you enter the(*)required information where indicate.HTML code is not allowed