J'ai d'abord appris C #, et maintenant je commence avec C ++. Si je comprends bien, l'opérateur new
en C ++ n'est pas similaire à celui en C #.
Pouvez-vous expliquer la raison de la fuite de mémoire dans cet exemple de code?
class A { ... };
struct B { ... };
A *object1 = new A();
B object2 = *(new B());
Réponses:
Qu'est-ce qui se passe
Lorsque vous écrivez,
T t;
vous créez un objet de typeT
avec une durée de stockage automatique . Il sera nettoyé automatiquement lorsqu'il sera hors de portée.Lorsque vous écrivez,
new T()
vous créez un objet de typeT
avec une durée de stockage dynamique . Il ne sera pas nettoyé automatiquement.Vous devez lui passer un pointeur
delete
pour le nettoyer:Cependant, votre deuxième exemple est pire: vous déréférencer le pointeur et faire une copie de l'objet. De cette façon, vous perdez le pointeur vers l'objet créé avec
new
, vous ne pouvez donc jamais le supprimer même si vous le souhaitez!Ce que tu devrais faire
Vous devriez préférer la durée de stockage automatique. Besoin d'un nouvel objet, écrivez simplement:
Si vous avez besoin d'une durée de stockage dynamique, stockez le pointeur vers l'objet alloué dans un objet de durée de stockage automatique qui le supprime automatiquement.
C'est un idiome courant qui porte le nom peu descriptif RAII ( Resource Acquisition Is Initialization ). Lorsque vous acquérez une ressource qui doit être nettoyée, vous la collez dans un objet de durée de stockage automatique afin que vous n'ayez pas à vous soucier de son nettoyage. Cela s'applique à n'importe quelle ressource, que ce soit la mémoire, les fichiers ouverts, les connexions réseau ou tout ce que vous voulez.
Cette
automatic_pointer
chose existe déjà sous diverses formes, je viens de la fournir à titre d'exemple. Une classe très similaire existe dans la bibliothèque standard appeléestd::unique_ptr
.Il existe également un ancien (pré-C ++ 11) nommé,
auto_ptr
mais il est maintenant obsolète car il a un comportement de copie étrange.Et puis il y a des exemples encore plus intelligents, comme
std::shared_ptr
, qui autorisent plusieurs pointeurs vers le même objet et ne le nettoient que lorsque le dernier pointeur est détruit.la source
*p += 2
, comme vous le feriez avec un pointeur normal. S'il ne retournait pas par référence, il n'imiterait pas le comportement d'un pointeur normal, ce qui est l'intention ici.Une explication étape par étape:
Donc, à la fin de cela, vous avez un objet sur le tas sans pointeur vers lui, il est donc impossible de le supprimer.
L'autre échantillon:
est une fuite de mémoire uniquement si vous oubliez
delete
la mémoire allouée:En C ++, il existe des objets avec stockage automatique, ceux créés sur la pile, qui sont automatiquement supprimés, et des objets avec stockage dynamique, sur le tas, avec lesquels vous allouez
new
et avec lesquels vous devez vous libérerdelete
. (tout cela est grosso modo)Pensez que vous devriez avoir un
delete
pour chaque objet alloué avecnew
.ÉDITER
À bien y penser,
object2
cela ne doit pas être une fuite de mémoire.Le code suivant est juste pour faire un point, c'est une mauvaise idée, n'aime jamais le code comme celui-ci:
Dans ce cas, puisqu'il
other
est passé par référence, ce sera l'objet exact pointé parnew B()
. Par conséquent, obtenir son adresse&other
et supprimer le pointeur libérerait la mémoire.Mais je ne saurais trop insister sur ce point, ne le faites pas. C'est juste ici pour faire un point.
la source
Étant donné deux "objets":
Ils n'occuperont pas le même emplacement en mémoire. En d'autres termes,
&a != &b
Attribuer la valeur de l'un à l'autre ne changera pas leur emplacement, mais cela changera leur contenu:
Intuitivement, les "objets" pointeurs fonctionnent de la même manière:
Maintenant, regardons votre exemple:
Il s'agit d'attribuer la valeur de
new A()
àobject1
. La valeur est un pointeur, signifiantobject1 == new A()
, mais&object1 != &(new A())
. (Notez que cet exemple n'est pas un code valide, c'est uniquement à titre d'explication)Comme la valeur du pointeur est préservée, nous pouvons libérer la mémoire vers laquelle il pointe:
delete object1;
En raison de notre règle, cela se comporte de la même manière que celuidelete (new A());
qui n'a pas de fuite.Pour votre deuxième exemple, vous copiez l'objet pointé. La valeur est le contenu de cet objet, pas le pointeur réel. Comme dans tous les autres cas
&object2 != &*(new A())
,.Nous avons perdu le pointeur vers la mémoire allouée et nous ne pouvons donc pas la libérer.
delete &object2;
peut sembler fonctionner, mais parce que&object2 != &*(new A())
ce n'est pas équivalentdelete (new A())
et donc invalide.la source
En C # et Java, vous utilisez new pour créer une instance de n'importe quelle classe et vous n'avez pas à vous soucier de la détruire plus tard.
C ++ a également un mot-clé "new" qui crée un objet mais contrairement à Java ou C #, ce n'est pas le seul moyen de créer un objet.
C ++ a deux mécanismes pour créer un objet:
Avec la création automatique, vous créez l'objet dans un environnement délimité: - dans une fonction ou - en tant que membre d'une classe (ou struct).
Dans une fonction, vous la créeriez de cette façon:
Dans une classe, vous le créez normalement de cette façon:
Dans le premier cas, les objets sont détruits automatiquement à la sortie du bloc de portée. Cela peut être une fonction ou un bloc de portée dans une fonction.
Dans ce dernier cas, l'objet b est détruit avec l'instance de A dont il est membre.
Les objets sont alloués avec new lorsque vous devez contrôler la durée de vie de l'objet, puis il faut supprimer pour le détruire. Avec la technique connue sous le nom de RAII, vous vous occupez de la suppression de l'objet au moment où vous le créez en le plaçant dans un objet automatique et attendez que le destructeur de cet objet automatique prenne effet.
Un de ces objets est un shared_ptr qui invoquera une logique "deleter" mais uniquement lorsque toutes les instances du shared_ptr qui partagent l'objet sont détruites.
En général, alors que votre code peut avoir de nombreux appels à new, vous devriez avoir des appels limités à supprimer et vous devez toujours vous assurer que ceux-ci sont appelés à partir de destructeurs ou d'objets "deleter" placés dans des pointeurs intelligents.
Vos destructeurs ne devraient également jamais lancer d'exceptions.
Si vous faites cela, vous aurez peu de fuites de mémoire.
la source
automatic
etdynamic
. Il y a aussistatic
.Cette ligne est la cause de la fuite. Essayons de séparer cela un peu.
object2 est une variable de type B, stockée à l'adresse 1 par exemple (Oui, je choisis des nombres arbitraires ici). Sur le côté droit, vous avez demandé un nouveau B, ou un pointeur vers un objet de type B. Le programme vous le donne volontiers et affecte votre nouveau B à l'adresse 2 et crée également un pointeur dans l'adresse 3. Maintenant, le seul moyen d'accéder aux données de l'adresse 2 est d'utiliser le pointeur de l'adresse 3. Ensuite, vous avez déréférencé le pointeur en utilisant
*
pour obtenir les données vers lesquelles le pointeur pointe (les données de l'adresse 2). Cela crée effectivement une copie de ces données et les affecte à object2, attribué à l'adresse 1. Rappelez-vous, c'est une COPIE, pas l'original.Maintenant, voici le problème:
Vous n'avez jamais stocké ce pointeur partout où vous pouvez l'utiliser! Une fois cette affectation terminée, le pointeur (mémoire dans adresse3, que vous avez utilisé pour accéder à adresse2) est hors de portée et hors de votre portée! Vous ne pouvez plus appeler la suppression sur celui-ci et ne pouvez donc pas nettoyer la mémoire dans address2. Il vous reste une copie des données de l'adresse2 dans l'adresse1. Deux des mêmes choses en mémoire. L'un auquel vous pouvez accéder, l'autre vous ne pouvez pas (parce que vous avez perdu le chemin). C'est pourquoi il s'agit d'une fuite de mémoire.
Je suggérerais, en venant de votre arrière-plan C #, que vous lisiez beaucoup sur le fonctionnement des pointeurs en C ++. Ils sont un sujet avancé et peuvent prendre un certain temps à appréhender, mais leur utilisation vous sera inestimable.
la source
Si cela vous facilite la tâche, considérez la mémoire informatique comme un hôtel et les programmes sont des clients qui louent des chambres quand ils en ont besoin.
La façon dont cet hôtel fonctionne est que vous réservez une chambre et prévenez le portier de votre départ.
Si vous programmez réserver une chambre et quittez sans en informer le portier, celui-ci pensera que la chambre est toujours utilisée et ne laissera personne d'autre l'utiliser. Dans ce cas, il y a une fuite dans la pièce.
Si votre programme alloue de la mémoire et ne la supprime pas (il cesse simplement de l'utiliser), l'ordinateur pense que la mémoire est toujours utilisée et ne permettra à personne d'autre de l'utiliser. Il s'agit d'une fuite de mémoire.
Ce n'est pas une analogie exacte, mais cela pourrait aider.
la source
Lors de la création,
object2
vous créez une copie de l'objet que vous avez créé avec new, mais vous perdez également le pointeur (jamais attribué) (il n'y a donc aucun moyen de le supprimer plus tard). Pour éviter cela, vous devez faireobject2
une référence.la source
Eh bien, vous créez une fuite de mémoire si vous ne libérez pas à un moment donné la mémoire que vous avez allouée à l'aide de l'
new
opérateur en passant un pointeur vers cette mémoire à l'delete
opérateur.Dans vos deux cas ci-dessus:
Ici, vous n'utilisez pas
delete
pour libérer de la mémoire, donc si et quand votreobject1
pointeur est hors de portée, vous aurez une fuite de mémoire, car vous aurez perdu le pointeur et ne pourrez donc pas utiliser l'delete
opérateur dessus.Et ici
vous ignorez le pointeur renvoyé par
new B()
et ne pouvez donc jamais passer ce pointeur àdelete
pour que la mémoire soit libérée. D'où une autre fuite de mémoire.la source
C'est cette ligne qui fuit immédiatement:
Ici, vous créez un nouveau
B
objet sur le tas, puis créez une copie sur la pile. Celui qui a été alloué sur le tas n'est plus accessible et donc la fuite.Cette ligne ne fuit pas immédiatement:
Il y aurait une fuite si vous
delete
n'as-object1
bien.la source