pointeurs "intelligents" (désolé, c'est leur nom) [C++] - C++ - Programmation
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
Sinon, j'ai une remarque deja :
Code :
|
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
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
|
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 :
|
(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++ ?".
Marsh Posté le 12-12-2002 à 11:51:05
nraynaud a écrit : |
facile, suffit de faire des sujets : "comment je peux overclocker mon PC en Eiffel ?"
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! |
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 ?
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
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 ?
Marsh Posté le 12-12-2002 à 12:17:25
nraynaud a écrit : |
fais ce qui te plait. mais pour moi l'auto-destruction, c'est ce tirer dans le pied
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 ...
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
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" |
c plutot twa qui m'enerve
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
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
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
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)
Marsh Posté le 13-12-2002 à 01:21:26
Moi j'utilise ceux là :
http://www.boost.org/libs/smart_ptr/index.htm
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.
Marsh Posté le 13-12-2002 à 04:13:53
wpk a écrit : pour le delete this, |
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 :
|
merci pour ces commentaires, j'espère que vous en avez d'autres ... y'a sûrement encore moyen de faire mieux.
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.
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.
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.
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 : |
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 : |
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.
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.
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 :
|
Marsh Posté le 14-12-2002 à 02:46:16
Mais... il dit du mal de C++
Avec des arguments pertinents en plus
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...
Marsh Posté le 14-12-2002 à 05:30:37
Musaran a écrit : Mais... il dit du mal de C++ |
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.
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.
Marsh Posté le 14-12-2002 à 13:32:35
wpk 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.
Marsh Posté le 14-12-2002 à 15:10:56
nraynaud 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.
Marsh Posté le 14-12-2002 à 16:32:32
wpk a écrit : |
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).
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.
ç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