De la bonne utilisation de new [C++] - C++ - Programmation
Marsh Posté le 28-10-2002 à 14:46:56
R3g a écrit a écrit : J'en suis arrivé à un point dans mon apprentissage du C++ ou je me pose pas mal de questions sur les bonnes pratiques de ce langage. Ayant appris la prog avec Java, la gestion de la memoire est deroutante pour moi, et j'hésite toujours beaucoup avant de déclarer un nouvel objet. En effet même si mon passé javaiste me pousse à utiliser new tout le temps, je me dit aussi qu'il est souvent preferable et plus simple de créer l'objet sur la pile. Alors y'a-t-il une "règle d'or" pour determiner si un objet doit être alloué sur le tas ou sur la pile, et quand on a le choix, qu'est-ce qui est le mieux ? Question subsidiaire découlant de la première : est-il préférable de passer un pointeur ou une référence en paramètre d'une fonction ? |
ca revient au meme, le passage par reference etant convertit en pointeurs, regarde ce que le compilateur genere apres la compilation. (en tout cas c ce que jai constate)
Marsh Posté le 28-10-2002 à 16:03:37
En C++, l'appel au constructeur est souvent plus coûteux que l'allocation mémoire proprement dite (à moins que le code du constructeur soit vraiment simple)...
Par contre, la règle du bon samaritain, pardon, du bon programmeur, en C++ est différente de celle en Java : en C++, c'est "moins on a de pointeurs, mieux on se porte"...
En moyenne, 70 % des bugs dans un programme C ou C++ sont des bugs mémoire liés à une mauvaise utilisation des pointeurs.
Marsh Posté le 28-10-2002 à 16:51:08
BifaceMcLeOD a écrit a écrit : En C++, l'appel au constructeur est souvent plus coûteux que l'allocation mémoire proprement dite (à moins que le code du constructeur soit vraiment simple)... Par contre, la règle du bon samaritain, pardon, du bon programmeur, en C++ est différente de celle en Java : en C++, c'est "moins on a de pointeurs, mieux on se porte"... En moyenne, 70 % des bugs dans un programme C ou C++ sont des bugs mémoire liés à une mauvaise utilisation des pointeurs. |
ok dans le cadre d'utilisation d'un objet
je pensai que cetait lors dun simple passage de parametre pour une fct
Marsh Posté le 28-10-2002 à 16:59:20
BifaceMcLeOD a écrit a écrit : En C++, l'appel au constructeur est souvent plus coûteux que l'allocation mémoire proprement dite (à moins que le code du constructeur soit vraiment simple)... Par contre, la règle du bon samaritain, pardon, du bon programmeur, en C++ est différente de celle en Java : en C++, c'est "moins on a de pointeurs, mieux on se porte"... En moyenne, 70 % des bugs dans un programme C ou C++ sont des bugs mémoire liés à une mauvaise utilisation des pointeurs. |
C quoi tu t'appels l'allocation mémoire proprement dite ?
Marsh Posté le 28-10-2002 à 17:03:08
R3g a écrit a écrit : J'en suis arrivé à un point dans mon apprentissage du C++ ou je me pose pas mal de questions sur les bonnes pratiques de ce langage. Ayant appris la prog avec Java, la gestion de la memoire est deroutante pour moi, et j'hésite toujours beaucoup avant de déclarer un nouvel objet. En effet même si mon passé javaiste me pousse à utiliser new tout le temps, je me dit aussi qu'il est souvent preferable et plus simple de créer l'objet sur la pile. Alors y'a-t-il une "règle d'or" pour determiner si un objet doit être alloué sur le tas ou sur la pile, et quand on a le choix, qu'est-ce qui est le mieux ? Question subsidiaire découlant de la première : est-il préférable de passer un pointeur ou une référence en paramètre d'une fonction ? |
Si tu peux te passer d'un pointeur, passe t'en.
Genre, si t'as le choix entre :
CClasse truc ("Coucou" );
ou
CClass* pTruc = new CClasse ("Coucou" );
Choisis la 1ère solution.
Pour ta question subsidiaire, j'te conseille de plutot utiliser les références : t'es sur qu'elle ne pointe pas vers qqch de null. En effet, en C++ (contrairement au Java), une référence ne peut pas être nulle, alors qu'un poiteur, SI. ça te fait un test en moins à chaque fois. c pas grand chose ms c toujours ça !
Marsh Posté le 28-10-2002 à 17:11:39
On rentre dans une notion de propriétaire ici. Globalement, pose toi la question de : qui est le propriétaire de l'objet et donc, qui devra se charger de le désalouer. En général, tu l'alloue sur la pile car c'est la méthode la plus simple et la plus sure pour éviter les fuites mémoires. Mais si tu passes la propriété de l'objet à un autre objet ( tu le mets dans une liste qui est chargée de désalouer elle même les éléments qu'elle contient par exemple ), alors tu utilises new.
Marsh Posté le 28-10-2002 à 18:27:14
Donc je comprends que mon intuition est bonne : si je peux me passer des pointeurs, je m'en passe.
Marsh Posté le 28-10-2002 à 19:06:43
El_Gringo a écrit a écrit : Pour ta question subsidiaire, j'te conseille de plutot utiliser les références : t'es sur qu'elle ne pointe pas vers qqch de null. En effet, en C++ (contrairement au Java), une référence ne peut pas être nulle, alors qu'un poiteur, SI. ça te fait un test en moins à chaque fois. c pas grand chose ms c toujours ça ! |
bien sur que non, pas plus avec une reference qu'avec un pointeur tu peux etre assure de travailler sur une reference valide! Ca n'a strictement rien a voir. Ce n'est pas en supprimant les pointeurs qu'on supprime les problemes (d'allocation notamment) !!
Le principal interet des references c'est pour l'utilisation de la semantique des passages par valeur et autres surcharges d'operateurs.
ex: vaut-il mieux ecrire
Code :
|
Code :
|
Code :
|
Et aussi une reference est "constante" au sens ou on ne peut pas changer la case memoire a laquelle elle fait reference, en fait on peut mais au prix d'un hack pas trop dans l'esprit du C++.
Ce qui veut dire que cela impose des regles notamment sur la construction des objets ayant des references membres et qu'un objet pointe par reference doit exister avant le referencement.
Ca c'est la theorie puisqu'en realite certains hackers vont deliberement faire une reference sur la case 0.. et certains debutants vont vouloir utiliser des references sur des objets qui n'existent plus.. (et parfois des non debutants aussi)
Sinon pour ce qui est des possibilites, les references
et les pointeurs sont assez souvent redondants, a tel point que Bjarne (oh je me repete) avait suggere qu'on puisse se passer des pointeurs dans un C++ ideal.
(et je suppose remplacer les pointeurs par des iterateurs pour tout ce qui est collection d'objets).
Conclusion : utilisez les references c'est une bonne pratique,
mais ne croyez pas que ca va faire le boulot a votre place.
A+
LeGreg
Marsh Posté le 28-10-2002 à 19:34:26
Code :
|
reponse de normand: ca depend.
l'allocation/desallocation sur la pile est ce qu'il y a de plus rapide (un compteur a incrementer ou desincrementer) c'est pour ca qu'on l'a introduit. Compare a un allocateur sur le tas qui doit faire un tas d'operations compliquees pour pister tous les blocs alloues en maintenant la fragmentation basse (sur certaines plateformes), et continuer a etre thread safe (ce qui peut rallentir pas mal l'allocation).
Pourtant, il n'est pas conseille d'utiliser la pile abusivement. Tout d'abord parce que sur un certain nombre de plateforme la taille de la pile est plus limitee que la taille du tas, et si tu alloues trop de donnees sur la pile ou si tu fais trop d'appel recursifs (ce qui revient au meme), ton programme va crasher. Il faut donc utiliser la pile "raisonablement" (dans les limites de ton systeme) mais ne pas trop se priver tout de meme, elle est la pour etre utilisee.
Sinon il y a une difference majeure entre l'allocation sur le tas et sur la pile, c'est que l'une des deux allocations est "dynamique auto" et l'autre est "dynamique manuelle".
Cela veut dire que l'allocation/desallocation sur la pile est prise en charge par le compilateur et tu ne peux rien y changer.
C'est a dire le plus souvent debut du bloc/fin du bloc. Au dela ton objet n'existe plus. Si tu veux etendre l'existence d'un objet (alloue dynamiquement, je ne parle evidemment pas des donnees statiques) tu es oblige d'utiliser l'allocation sur le tas.
La pile n'est pas trop threadsafe (sauf si tu imposes des contraintes de synchronisation fortes). Si tu veux partager des donnees entre plusieurs threads utilise l'allocation statique ou dynamique manuelle.
A+
LeGreg
Marsh Posté le 28-10-2002 à 21:00:43
Ok merci pour toutes ces réponses. De fait la question des threads ne m'était pas venu à l'esprit dans ce contexte. Par contre j'ai déja vu un depassement de pile à cause d'un appel récursif, c'est d'ailleurs une des raisons qui m'ont amené à me poser ces questions.
Marsh Posté le 29-10-2002 à 01:17:19
Par défaut, créer les objets sur la pile.
Je connaît ces raisons d'allouer sur le tas:
-Maîtriser la durée de vie. Typiquement les objets d'interface utilisateur.
-Taille/nombre dynamique (inconnu à la compilation).
-Trop grand pour la pile.
Par défaut, utiliser les références.
Je connaît ces raisons d'utiliser un pointeur:
-Changer le référent.
-Arithmétique des pointeurs.
-Compatibilité C.
-Indirections multiples.
-Valeur invalide.
legreg a écrit : bien sur que non, pas plus avec une reference qu'avec un pointeur tu peux etre assure de travailler sur une reference valide! |
Soyons pointilleux: il peut arriver (en programmant mal), qu'une référence désigne null ou un objet invalide.
Mais si cela arrive, c'est une erreur de celui qui l'a générée, pas une erreur de celui qui l'utilise.
Une référence n'est jamais censée être invalide, à aucun moment.
Citation : et qu'un objet pointe par reference doit exister avant le referencement. |
Ben tu vois que tu le sais...
Marsh Posté le 29-10-2002 à 01:27:44
Citation :
Ben tu vois que tu le sais... |
je me repete :
une reference demande la meme gymnastique pour la gestion de la duree de vie qu'un pointeur. Il n'y a aucun mecanisme magique derriere les references qui protege des references invalides.
A+
LeGreg
Marsh Posté le 29-10-2002 à 09:02:27
legreg a écrit a écrit : bien sur que non, pas plus avec une reference qu'avec un pointeur tu peux etre assure de travailler sur une reference valide! Ca n'a strictement rien a voir. Ce n'est pas en supprimant les pointeurs qu'on supprime les problemes (d'allocation notamment) !! |
T'as rien compris à ce que j'dit apparement...
Lors d'un passage de valeur par référence, l'objet dont la référence doit exister.
Illustration :
Passage par pointeur :
Code :
|
passage par référence :
Code :
|
...ok ?
Marsh Posté le 29-10-2002 à 10:06:34
Citation : passage par référence :
|
bien sur qu'il ne peut pas etre null puisque c'est une reference pas un pointeur mais ca ne veut pas dire que la reference est valide, de la meme maniere que ce n'est pas parce qu'un pointeur n'est pas null qu'il est valide!
On n'est pas en java,
d'apres toi pourquoi java s'embete avec les reference counted et la garbage collection, si les references ca fonctionnait tout seul comme en C++??
Bref faut pas tout melanger.
exemple d'erreurs de debutants (volontairement debile mais c'est pour que les gens comprennent):
Code :
|
Bien sur le coup de la reference d'un objet alloue sur la pile est classique et seuls les debutants se laissent prendre
mais que penser d'un objet alloue sur le tas dont ta classe MaClasse ne controle pas la duree de vie? Eh bien tu dois faire EXACTEMENT le meme raisonnement que sur les pointeurs pour t'assurer que tu manipules des donnees valides.
LeGreg
Marsh Posté le 29-10-2002 à 10:56:43
LeGreg > une reference est une autre forme syntaxique que le pointeur.
semantiquement la difference entre un pointeur et une reference, c'est que la reference se doit d'etre valide alors que le pointeur se doit d'etre valide ou NULL
Après qu'il y ait des erreurs de programmations, c'est inevitable, et il doit meme etre possible de mettre une reference à NULL en l'initialisant avec un pointeur.
Mastruct *p=NULL;
Mastruct &r=*p;
(a priori ces deux ligne ne doivent pas etre dans la meme fct, sinon c'est du sabotage )
on ne parle pas de ce qui est possible, mais plutot de la semantique associée
Marsh Posté le 29-10-2002 à 11:15:04
BENB a écrit a écrit : Mastruct *p=NULL; Mastruct &r=*p; (a priori ces deux ligne ne doivent pas etre dans la meme fct, sinon c'est du sabotage ) |
Là, normalement, un bon compilateur qui se respecte devrait pouvoir détecter cette erreur et émettre un Warning "Attention ! forte probabilité de faute mémoire".
En tout cas, d'autres langages savent le faire.
Marsh Posté le 29-10-2002 à 11:36:34
BifaceMcLeOD a écrit a écrit : Là, normalement, un bon compilateur qui se respecte devrait pouvoir détecter cette erreur et émettre un Warning "Attention ! forte probabilité de faute mémoire". En tout cas, d'autres langages savent le faire. |
ecrit comme ca oui...
dans le code c'est generalement dissimulé dans des appels de fonctions
Code :
|
Marsh Posté le 29-10-2002 à 12:51:29
Citation : Après qu'il y ait des erreurs de programmations, c'est inevitable, et il doit meme etre possible de mettre une reference à NULL en l'initialisant avec un pointeur. |
qu'il y ait une difference de syntaxe, je le souligne dans mon poste plus haut. Qu'une struct ne puisse pas etre a priori changee, ni pointer sur la case memoire zero, tout a fait d'accord (modulo les hacks pas beaux). qu'eliminer le test a zero d'un pointeur augmente la securite ou elimine les prises de tete, pas d'accord.
- Combien de fois un programmeur va passer un pointeur nul alors qu'il est sense passer un pointeur valide ? ca arrive et un simple assert suffit a debuguer ce cas. Le cas du pointeur nul est alors une aide au debogage (sauf si c'est une valeur d'argument acceptable dans ce cas ce n'est pas une erreur).
- Combien de fois un programmeur va passer/stocker un pointeur/reference sur un objet deja detruit ou en passe d'etre detruit? sans doute trop souvent s'il ne prend pas le temps de designer son application et la bonbon pour deboguer!
Faut-il dire qu'il faut utiliser les types non signes plutot que les types signes parce qu'une fonction peut considerer que les valeurs < 0 sont des valeurs invalides? Non, si dans ce cas, les valeurs < 0 sont invalides, la fonctionnalite d'une fonction acceptant les types signes sera la meme qu'une fonction reecrite acceptant les types non signes mais il faut prendre en compte que ce sera a l'utilisateur de la fonction de faire la conversion signee/non signee et de verifier que le signe >=0 lors de cette conversion! Quel est le gain? ca depend des cas..
Si l'utilisateur se contente d'ecrire des choses comme
call_function_byPtr(&maStruct); // maStruct un objet valide
ce sera aussi sur que s'il ecrit
call_function_byRef(maStruct); // maStruct est un objet valide
Si l'utilisateur decide d'ecrire des choses non sures comme:
call_function_byPtr(maStructPtr); // quid de la validite de maStructPtr?
ce sera autant non sur que d'ecrire:
call_function_byRef(*maStructPtr); // quid de la validite de *maStructPtr ?
LeGreg
Marsh Posté le 29-10-2002 à 14:15:46
legreg a écrit a écrit :
|
Je parle avant tout de semantique, pas de securite ou de prise de tete, la gestion de l'allocation de memoire en C++ reste ce qu'elle est : manuelle...
Quand une fonction prends ou retourne une reference, il est evident que la valeur NULL est inadaptée... c'est la différence de sens entre un pointeur et une reference.
Après passer un pointeur (ou une reference) invalide c'est pour moi une erreur de programmation... Un pointeur (ou une reference) ne devrait jamais etre invalide... sauf à pointer sur NULL ce qui n'est pas tout à fait la meme chose...
L'avantage de prendre une reference en argument est de bien signaler (toujours le sens) à l'utilisateur de la fonction que NULL n'est pas accepter, ce que ne fait pas l'assert
PS : personnellement j'utilise l'assert
Marsh Posté le 28-10-2002 à 14:15:11
J'en suis arrivé à un point dans mon apprentissage du C++ ou je me pose pas mal de questions sur les bonnes pratiques de ce langage. Ayant appris la prog avec Java, la gestion de la memoire est deroutante pour moi, et j'hésite toujours beaucoup avant de déclarer un nouvel objet. En effet même si mon passé javaiste me pousse à utiliser new tout le temps, je me dit aussi qu'il est souvent preferable et plus simple de créer l'objet sur la pile.
Alors y'a-t-il une "règle d'or" pour determiner si un objet doit être alloué sur le tas ou sur la pile, et quand on a le choix, qu'est-ce qui est le mieux ?
Question subsidiaire découlant de la première : est-il préférable de passer un pointeur ou une référence en paramètre d'une fonction ?
---------------
Au royaume des sourds, les borgnes sont sourds.