J'utilise C ++ depuis peu de temps et je me suis interrogé sur le nouveau mot clé. Dois-je simplement l'utiliser ou non?
1) Avec le nouveau mot-clé ...
MyClass* myClass = new MyClass();
myClass->MyField = "Hello world!";
2) Sans le nouveau mot-clé ...
MyClass myClass;
myClass.MyField = "Hello world!";
Du point de vue de l'implémentation, ils ne semblent pas si différents (mais je suis sûr qu'ils le sont) ... Cependant, mon langage principal est C #, et bien sûr la 1ère méthode est ce à quoi je suis habitué.
La difficulté semble être que la méthode 1 est plus difficile à utiliser avec les classes std C ++.
Quelle méthode dois-je utiliser?
Mise à jour 1:
J'ai récemment utilisé le nouveau mot clé pour la mémoire de tas (ou magasin gratuit ) pour un grand tableau qui sortait du cadre (c'est-à-dire qui était renvoyé par une fonction). Là où auparavant j'utilisais la pile, ce qui provoquait la corruption de la moitié des éléments en dehors de la portée, le passage à l'utilisation du tas garantissait que les éléments étaient en contact. Yay!
Mise à jour 2:
Un de mes amis m'a récemment dit qu'il y avait une règle simple pour utiliser le new
mot - clé; chaque fois que vous tapez new
, tapez delete
.
Foobar *foobar = new Foobar();
delete foobar; // TODO: Move this to the right place.
Cela permet d'éviter les fuites de mémoire, car vous devez toujours placer la suppression quelque part (c'est-à-dire lorsque vous la coupez et la collez sur un destructeur ou autre).
std::vector
etstd::shared_ptr
. Ceux-ci enveloppent les appels versnew
etdelete
pour vous, de sorte que vous êtes encore moins susceptible de perdre de la mémoire. Demandez-vous par exemple: vous souvenez-vous toujours de mettre un correspondantdelete
partout où une exception pourrait être levée? Mettre desdelete
s à la main est plus difficile que vous ne le pensez.Réponses:
Méthode 1 (en utilisant
new
)delete
votre objet plus tard. (Si vous ne le supprimez pas, vous pourriez créer une fuite de mémoire)delete
. (c.-à-d. vous pourriezreturn
un objet que vous avez créé en utilisantnew
)delete
d; et il doit toujours être supprimé , quel que soit le chemin de contrôle utilisé ou si des exceptions sont levées.Méthode 2 (ne pas utiliser
new
)delete
plus tard.return
pointer vers un objet de la pile)En ce qui concerne lequel utiliser; vous choisissez la méthode qui vous convient le mieux, compte tenu des contraintes ci-dessus.
Quelques cas faciles:
delete
(et du risque de provoquer des fuites de mémoire ), vous ne devriez pas utilisernew
.new
la source
Il y a une différence importante entre les deux.
Tout ce qui n'est pas alloué avec
new
se comporte comme les types de valeur en C # (et les gens disent souvent que ces objets sont alloués sur la pile, ce qui est probablement le cas le plus courant / évident, mais pas toujours vrai. Plus précisément, les objets alloués sans utilisernew
ont un stockage automatique duration Tout alloué avecnew
est alloué sur le tas, et un pointeur est retourné, exactement comme les types de référence en C #.Tout ce qui est alloué sur la pile doit avoir une taille constante, déterminée au moment de la compilation (le compilateur doit définir correctement le pointeur de pile, ou si l'objet est membre d'une autre classe, il doit ajuster la taille de cette autre classe) . C'est pourquoi les tableaux en C # sont des types de référence. Ils doivent l'être, car avec les types de référence, nous pouvons décider au moment de l'exécution de la quantité de mémoire à demander. Et la même chose s'applique ici. Seuls les tableaux de taille constante (une taille qui peut être déterminée au moment de la compilation) peuvent être alloués avec une durée de stockage automatique (sur la pile). Les tableaux de taille dynamique doivent être alloués sur le tas, en appelant
new
.(Et c'est là que toute similitude avec C # s'arrête)
Maintenant, tout ce qui est alloué sur la pile a une durée de stockage "automatique" (vous pouvez en fait déclarer une variable comme
auto
, mais c'est la valeur par défaut si aucun autre type de stockage n'est spécifié, donc le mot-clé n'est pas vraiment utilisé dans la pratique, mais c'est là qu'il vient de)La durée de stockage automatique signifie exactement à quoi cela ressemble, la durée de la variable est gérée automatiquement. En revanche, tout ce qui est alloué sur le tas doit être supprimé manuellement par vous. Voici un exemple:
Cette fonction crée trois valeurs à considérer:
Sur la ligne 1, il déclare une variable
b
de typebar
sur la pile (durée automatique).Sur la ligne 2, il déclare un
bar
pointeurb2
sur la pile (durée automatique), et appelle new, allouant unbar
objet sur le tas. (durée dynamique)Lorsque la fonction revient, les événements suivants se produisent: Premièrement,
b2
sort du domaine d'application (l'ordre de destruction est toujours opposé à l'ordre de construction). Mais ceb2
n'est qu'un pointeur, donc rien ne se passe, la mémoire qu'il occupe est simplement libérée. Et surtout, la mémoire vers laquelle il pointe (l'bar
instance sur le tas) n'est PAS touchée. Seul le pointeur est libéré, car seul le pointeur a une durée automatique. Deuxièmement,b
sort du domaine, donc comme il a une durée automatique, son destructeur est appelé et la mémoire est libérée.Et l'
bar
instance sur le tas? C'est probablement toujours là. Personne n'a pris la peine de le supprimer, nous avons donc perdu de la mémoire.De cet exemple, nous pouvons voir que tout ce qui a une durée automatique est garanti d'avoir son destructeur appelé quand il sort du domaine. C'est utile. Mais tout ce qui est alloué sur le tas dure aussi longtemps que nous en avons besoin et peut être dimensionné dynamiquement, comme dans le cas des tableaux. C'est également utile. Nous pouvons l'utiliser pour gérer nos allocations de mémoire. Et si la classe Foo allouait de la mémoire sur le tas dans son constructeur et supprimait cette mémoire dans son destructeur. Ensuite, nous pourrions obtenir le meilleur des deux mondes, des allocations de mémoire sûres qui sont garanties d'être à nouveau libérées, mais sans les limitations de forcer tout à être sur la pile.
Et c'est à peu près exactement comment fonctionne la plupart du code C ++. Regardez
std::vector
par exemple la bibliothèque standard . Cela est généralement alloué sur la pile, mais peut être dimensionné et redimensionné dynamiquement. Et il le fait en allouant en interne de la mémoire sur le tas si nécessaire. L'utilisateur de la classe ne voit jamais cela, il n'y a donc aucun risque de fuite de mémoire ou d'oubli de nettoyer ce que vous avez alloué.Ce principe est appelé RAII (Resource Acquisition is Initialization), et il peut être étendu à toute ressource qui doit être acquise et libérée. (sockets réseau, fichiers, connexions à la base de données, verrous de synchronisation). Tous peuvent être acquis dans le constructeur et libérés dans le destructeur, vous avez donc la garantie que toutes les ressources que vous acquérez seront à nouveau libérées.
En règle générale, n'utilisez jamais new / delete directement à partir de votre code de haut niveau. Enveloppez-le toujours dans une classe qui peut gérer la mémoire pour vous et qui garantira qu'elle sera à nouveau libérée. (Oui, il peut y avoir des exceptions à cette règle. En particulier, les pointeurs intelligents vous obligent à appeler
new
directement et à passer le pointeur à son constructeur, qui prend alors le relais et s'assure qu'ildelete
est appelé correctement. Mais c'est toujours une règle d'or très importante )la source
Ceci n'est presque jamais déterminé par vos préférences de frappe mais par le contexte. Si vous devez conserver l'objet sur plusieurs piles ou s'il est trop lourd pour la pile, vous l'allouez sur la boutique gratuite. De plus, puisque vous allouez un objet, vous êtes également responsable de libérer la mémoire. Recherchez l'
delete
opérateur.Pour alléger le fardeau de l'utilisation de la gestion des magasins gratuits, les gens ont inventé des trucs comme
auto_ptr
etunique_ptr
. Je vous recommande fortement de les consulter. Ils pourraient même être utiles pour vos problèmes de frappe ;-)la source
Si vous écrivez en C ++, vous écrivez probablement pour des performances. L'utilisation de new et de la boutique gratuite est beaucoup plus lente que l'utilisation de la pile (en particulier lorsque vous utilisez des threads), utilisez-la uniquement lorsque vous en avez besoin.
Comme d'autres l'ont dit, vous avez besoin de nouveautés lorsque votre objet doit vivre en dehors de la portée de la fonction ou de l'objet, l'objet est vraiment grand ou lorsque vous ne connaissez pas la taille d'un tableau au moment de la compilation.
Essayez également d'éviter de supprimer. Enveloppez plutôt votre nouveau dans un pointeur intelligent. Laissez l'appel du pointeur intelligent supprimer pour vous.
Dans certains cas, un pointeur intelligent n'est pas intelligent. Ne stockez jamais std :: auto_ptr <> dans un conteneur STL. Il supprimera le pointeur trop tôt en raison d'opérations de copie à l'intérieur du conteneur. Un autre cas est celui où vous avez un très grand conteneur STL de pointeurs vers des objets. boost :: shared_ptr <> aura une surcharge de vitesse car il augmente le nombre de références de haut en bas. La meilleure façon de procéder dans ce cas est de placer le conteneur STL dans un autre objet et de donner à cet objet un destructeur qui appellera delete sur chaque pointeur du conteneur.
la source
La réponse courte est: si vous êtes un débutant en C ++, vous ne devriez jamais utiliser
new
nidelete
vous - même.Au lieu de cela, vous devez utiliser des pointeurs intelligents tels que
std::unique_ptr
etstd::make_unique
(ou moins souvent,std::shared_ptr
etstd::make_shared
). De cette façon, vous n'avez pas à vous soucier autant des fuites de mémoire. Et même si vous êtes plus avancé, la meilleure pratique consiste généralement à encapsuler la manière personnalisée que vous utiliseznew
etdelete
dans une petite classe (comme un pointeur intelligent personnalisé) dédiée uniquement aux problèmes de cycle de vie des objets.Bien sûr, en arrière-plan, ces pointeurs intelligents effectuent toujours l'allocation dynamique et la désallocation, de sorte que le code qui les utilise aurait toujours le temps d'exécution associé. D'autres réponses ici ont couvert ces problèmes et comment prendre des décisions de conception sur le moment d'utiliser des pointeurs intelligents par rapport à la simple création d'objets sur la pile ou à leur incorporation en tant que membres directs d'un objet, suffisamment pour que je ne les répète pas. Mais mon résumé serait: n'utilisez pas de pointeurs intelligents ou d'allocation dynamique jusqu'à ce que quelque chose vous y oblige.
la source
Sans le
new
mot clé, vous le stockez sur la pile des appels . Le stockage de variables trop grandes sur la pile entraînera un débordement de pile .la source
La réponse simple est oui - new () crée un objet sur le tas (avec l'effet secondaire malheureux que vous devez gérer sa durée de vie (en appelant explicitement delete dessus), tandis que le second formulaire crée un objet dans la pile dans le courant) portée et cet objet sera détruit quand il sort de la portée.
la source
Si votre variable n'est utilisée que dans le contexte d'une seule fonction, il vaut mieux utiliser une variable de pile, c'est-à-dire l'option 2. Comme d'autres l'ont dit, vous n'avez pas à gérer la durée de vie des variables de pile - elles sont construites et détruit automatiquement. En outre, l'allocation / désallocation d'une variable sur le tas est lente en comparaison. Si votre fonction est appelée assez souvent, vous constaterez une amélioration considérable des performances si vous utilisez des variables de pile par rapport aux variables de tas.
Cela dit, il existe quelques exemples évidents où les variables de pile sont insuffisantes.
Si la variable de pile a une grande empreinte mémoire, vous courez le risque de déborder la pile. Par défaut, la taille de pile de chaque thread est de 1 Mo sous Windows. Il est peu probable que vous créiez une variable de pile d'une taille de 1 Mo, mais vous devez garder à l'esprit que l'utilisation de la pile est cumulative. Si votre fonction appelle une fonction qui appelle une autre fonction qui appelle une autre fonction qui ..., les variables de pile dans toutes ces fonctions prennent de la place sur la même pile. Les fonctions récursives peuvent rencontrer rapidement ce problème, selon la profondeur de la récursivité. En cas de problème, vous pouvez augmenter la taille de la pile (non recommandé) ou allouer la variable sur le tas à l'aide du nouvel opérateur (recommandé).
L'autre condition, plus probable, est que votre variable doit "vivre" au-delà de la portée de votre fonction. Dans ce cas, vous alloueriez la variable sur le tas afin qu'elle puisse être atteinte en dehors de la portée d'une fonction donnée.
la source
Vous passez myClass hors d'une fonction, ou vous attendez à ce qu'elle existe en dehors de cette fonction? Comme certains l'ont dit, il s'agit de portée lorsque vous n'allouez pas sur le tas. Lorsque vous quittez la fonction, elle disparaît (éventuellement). L'une des erreurs classiques commises par les débutants est la tentative de créer un objet local d'une classe dans une fonction et de le renvoyer sans l'allouer sur le tas. Je me souviens avoir débogué ce genre de chose dans mes premiers jours en faisant C ++.
la source
La deuxième méthode crée l'instance sur la pile, ainsi que des éléments déclarés
int
et la liste des paramètres passés dans la fonction.La première méthode fait de la place pour un pointeur sur la pile, que vous avez défini à l'emplacement en mémoire où un nouveau
MyClass
a été alloué sur le tas - ou magasin gratuit.La première méthode requiert également que vous
delete
créez avecnew
, tandis que dans la deuxième méthode, la classe est automatiquement détruite et libérée lorsqu'elle tombe hors de portée (l'accolade de fermeture suivante, généralement).la source
La réponse courte est oui, le "nouveau" mot clé est incroyablement important car lorsque vous l'utilisez, les données d'objet sont stockées sur le tas par opposition à la pile, ce qui est le plus important!
la source