Est-ce une bonne pratique de supprimer un pointeur après l'avoir supprimé?

je vais commencer par dire, utilisez des pointeurs intelligents et vous n'aurez jamais à vous inquiéter à ce sujet.

Quels sont les problèmes avec le code suivant?

Foo * p = new Foo;
// (use p)
delete p;
p = NULL;

cela a été provoqué par une réponse et des commentaires à une autre question. Un commentaire de Neil Butterworth a généré quelques notes positives:

paramétrage des pointeurs à NULL la suppression suivante n'est pas une bonne pratique universelle en C++. Il y a des moments où c'est une bonne chose à faire, et des fois lorsque c'est inutile et peut masquer les erreurs.

il y a beaucoup de circonstances où ça n'aiderait pas. Mais d'après mon expérience, ça ne peut pas faire de mal. Quelqu'un m'éclairer.

128
demandé sur Community 2009-12-19 01:48:05

18 réponses

définir un pointeur à 0 (qui est" null " dans le standard c++, le défini NULL DE C est quelque peu différent) évite les accidents sur les doubles suppressions.

considérer ce qui suit:

Foo* foo = 0; // Sets the pointer to 0 (C++ NULL)
delete foo; // Won't do anything

attendu que:

Foo* foo = new Foo();
delete foo; // Deletes the object
delete foo; // Undefined behavior 

en d'autres termes, si vous ne définissez pas les pointeurs supprimés à 0, Vous allez avoir des problèmes si vous faites des doubles suppressions. Un argument contre le réglage des pointeurs à 0 après Supprimer serait que le faire masque juste double supprimer les bogues et les laisse non manipulés.

il est préférable de ne pas avoir de bogues de suppression doubles, évidemment, mais en fonction de la sémantique de la propriété et des cycles de vie des objets, cela peut être difficile à réaliser dans la pratique. Je préfère un double bug d'effacement masqué à UB.

enfin, un sidenote concernant la gestion de l'allocation des objets, je vous suggère de jeter un oeil à std::unique_ptr pour la propriété stricte/singulière, std::shared_ptr pour la propriété partagée, ou un autre pointeur intelligent la mise en œuvre, en fonction de vos besoins.

75
répondu Håvard S 2015-03-10 22:50:05

placer des pointeurs à NULL après que vous avez supprimé ce qu'il a indiqué ne peut certainement pas faire de mal, mais il est souvent un peu d'un pansement sur un problème plus fondamental: pourquoi utilisez-vous un pointeur en premier lieu? Je peux voir deux raisons typiques:

  • vous vouliez simplement que quelque chose soit alloué sur le tas. Dans ce cas, l'envelopper dans un objet RAII aurait été beaucoup plus sûr et plus propre. Mettez fin à la portée de L'objet RAII lorsque vous n'avez plus besoin de l'objet. C'est comment std::vector fonctionne, et il résout le problème de laisser accidentellement des pointeurs à la mémoire désallouée autour. Il n'y a pas de pointeurs.
  • ou peut-être que vous vouliez une sémantique complexe de propriété partagée. Le pointeur retourné de new pourrait ne pas être le même que celui que delete est appelé. Plusieurs objets peuvent avoir utilisé l'objet simultanément dans l'intervalle. Dans ce cas, un pointeur partagé ou quelque chose de similaire aurait été préférable.

ma règle de base est que si vous laissez des pointeurs dans le code utilisateur, vous le faites mal. Le pointeur ne devrait pas être là pour pointer les ordures en premier lieu. Pourquoi n'y a-t-il pas un objet qui assume la responsabilité d'en assurer la validité? Pourquoi ne pas de son champ d'application fin, quand le fait-à l'objet?

53
répondu jalf 2009-12-18 22:55:08

j'ai une meilleure pratique encore: là où c'est possible, mettez fin à la portée de la variable!

{
    Foo* pFoo = new Foo;
    // use pFoo
    delete pFoo;
}
41
répondu Don Neufeld 2009-12-18 22:49:38

j'ai toujours mis un pointeur sur NULL (maintenant nullptr ) après avoir supprimé l'objet ou les objets qu'il pointe.

  1. " il peut aider à saisir de nombreuses références à la mémoire libérée (en supposant que votre plate-forme est défectueuse sur un deref d'un pointeur nul).

  2. il n'attrapera pas toutes les références à la mémoire libre si, par exemple, vous avez des copies du pointeur se trouvant autour. Mais certains sont mieux que rien.

  3. il masquera une double suppression, mais je trouve que celles-ci sont beaucoup moins communes que les accès à la mémoire déjà libérée.

  4. Dans de nombreux cas, le compilateur va optimiser loin. Donc l'argument que c'est inutile ne me convainc pas.

  5. si vous utilisez déjà RAII, alors il n'y a pas beaucoup de delete dans votre code pour commencer, donc l'argument que l'affectation supplémentaire provoque clutter ne me convainc pas.

  6. il est souvent commode, lors du débogage, de voir la valeur nulle plutôt qu'un pointeur périmé.

  7. si cela vous dérange encore, utilisez un pointeur intelligent ou une référence à la place.

j'ai aussi placé d'autres types de poignées de ressources à la valeur sans ressource quand la ressource est libre (qui est typiquement seulement dans le destructeur D'un RAII wrapper écrit pour encapsuler la ressource).

j'ai travaillé sur un grand (9 millions de déclarations) produit commercial (principalement en C). À un moment, nous avons utilisé la macro magic pour désactiver le pointeur lorsque la mémoire était libérée. Cela a immédiatement mis à jour de nombreux bogues qui ont été rapidement corrigés. Pour autant que je m'en souvienne, on n'a jamais eu de double bug.

mise à jour: Microsoft estime que c'est une bonne pratique pour la sécurité et recommande la pratique dans leurs politiques SDL. Apparemment MSVC++11 va piétiner le pointeur supprimé automatiquement (dans de nombreuses circonstances) si vous compilez avec L'option /SDL.

27
répondu Adrian McCarthy 2016-06-20 22:50:55

tout d'abord, il ya beaucoup de questions existantes sur ce sujet et des sujets étroitement liés, par exemple pourquoi ne pas supprimer le pointeur à NULL? .

dans votre code, la question ce qui se passe dans (utilisez p). Par exemple, si quelque part vous avez le code suivant:

Foo * p2 = p;

puis mettre P à NULL accomplit très peu, car vous avez encore le pointeur p2 à vous soucier.

cela ne veut pas dire que définir un pointeur à NULL est toujours inutile. Par exemple, si p était une variable membre pointant vers une ressource dont la durée de vie n'était pas exactement la même que la classe contenant p, alors mettre P à NULL pourrait être un moyen utile d'indiquer la présence ou l'absence de la ressource.

12
répondu Community 2017-05-23 11:47:09

S'il y a plus de code après le delete , Oui. Lorsque le pointeur est supprimé dans un constructeur ou à la fin de la méthode ou de la fonction, non.

le but de cette parabole est de rappeler au programmeur, pendant l'exécution, que l'objet a déjà été supprimé.

une pratique encore meilleure consiste à utiliser des pointeurs intelligents (partagés ou scopés) qui suppriment automatiquement leurs objets cibles.

7
répondu Thomas Matthews 2009-12-18 22:52:33

comme d'autres l'ont dit, delete ptr; ptr = 0; ne va pas faire sortir les démons de ton nez. Cependant, il encourage l'utilisation de ptr comme une sorte de drapeau. Le code est jonché de delete et le pointeur est réglé sur NULL . L'étape suivante est de disperser if (arg == NULL) return; à travers votre code pour vous protéger contre l'utilisation accidentelle d'un pointeur NULL . Le problème se produit Une fois que les contrôles contre NULL deviennent votre principal moyen de vérifier l'état d'un objet ou d'un programme.

je suis sûr qu'il y a une odeur de code à propos de l'utilisation d'un indicateur comme un drapeau quelque part mais je n'en ai pas trouvé.

3
répondu D.Shawley 2009-12-18 23:21:24

je vais changer légèrement votre question:

utiliseriez-vous un uninitialized pointeur? Vous savez, celui que vous n'avez pas défini à NULL ou allouer la mémoire il les points?

il y a deux scénarios où le réglage du pointeur à NULL peut être sauté:

  • la variable de pointeur sort immédiatement de la portée
  • vous avez surchargé la sémantique du pointeur et utilisent sa valeur non seulement comme un pointeur de mémoire, mais aussi comme une clé ou une valeur brute. cette approche souffre cependant d'autres problèmes.

pendant ce temps, faire valoir que le fait de mettre le pointeur sur NULL pourrait cacher des erreurs pour moi ressemble à faire valoir que vous ne devriez pas corriger un bogue parce que le correctif pourrait cacher un autre bogue. Les seuls bugs qui pourrait montrer si le pointeur n'est pas NULL seraient ceux qui tentent d'utiliser le pointeur. Mais le définir à NULL causerait en fait exactement le même bug que si vous l'utilisiez avec de la mémoire libérée, n'est-ce pas?

2
répondu Franci Penov 2009-12-18 23:04:50

si vous n'avez aucune autre contrainte qui vous oblige à définir ou non le pointeur à NULL après l'avoir supprimé (une de ces contraintes a été mentionnée par Neil Butterworth ), alors ma préférence personnelle est de laisser tomber.

Pour moi, la question n'est pas "est-ce une bonne idée?"mais "ce comportement aurais-je prévenir, ou permettre de réussir en faisant cela?"Par exemple, si cela permet à d'autres de code pour voir que le pointeur n'est plus disponible, pourquoi un autre code qui essaie même de regarder les pointeurs libérés une fois qu'ils sont libérés? Généralement, c'est un bug.

Il fait aussi plus de travail que nécessaire et entraver débogage post-mortem. Moins vous touchez la mémoire après vous n'en avez pas besoin, plus il est facile de comprendre pourquoi quelque chose s'est écrasé. Plusieurs fois, j'ai compté sur le fait que la mémoire est dans un état similaire à celui où un bogue particulier s'est produit pour diagnostiquer et corriger ledit bogue.

2
répondu MSN 2017-05-23 12:18:09

annule explicitement après suppression suggère fortement à un lecteur que le pointeur représente quelque chose qui est conceptuellement optionnel . Si je voyais cela fait, je commencerais à m'inquiéter que partout dans la source le pointeur soit utilisé qu'il devrait d'abord être testé contre NULL.

si c'est ce que vous voulez dire, il est préférable de le rendre explicite dans la source en utilisant quelque chose comme boost:: optional

optional<Foo*> p (new Foo);
// (use p.get(), but must test p for truth first!...)
delete p.get();
p = optional<Foo*>();

mais si vous voulez vraiment que les gens sachent que le pointeur a "mal tourné", je suis d'accord à 100% avec ceux qui disent que la meilleure chose à faire est de le faire sortir du cadre. Ensuite, vous utilisez le compilateur pour empêcher la possibilité de déréférences à l'exécution.

C'est le bébé dans toute l'eau du bain C++, ne devrait pas le jeter. :)

2
répondu HostileFork 2009-12-19 01:17:08

dans un programme bien structuré avec une vérification appropriée des erreurs, il n'y a aucune raison de ne pas de l'attribuer null. 0 est à lui seul une valeur invalide universellement reconnue dans ce contexte. L'échec dur et ne parviennent pas rapidement.

plusieurs des arguments contre l'assignation de 0 suggèrent que pourrait cacher un bug ou compliquer le flux de contrôle. Fondamentalement, il s'agit soit d'une erreur en amont (pas de votre faute le mauvais jeu de mots)) ou une autre erreur au nom du programmeur -- peut-être même une indication que le flux de programme est devenu trop complexe.

si le programmeur veut introduire l'utilisation d'un pointeur qui peut être nul comme valeur spéciale et écrire toute l'esquive nécessaire autour de cela, c'est une complication qu'ils ont délibérément introduit. Plus la quarantaine est efficace, plus tôt vous trouverez des cas de mauvais usage, et moins ils pourront se propager à d'autres programmes.

des programmes bien structurés peuvent être conçus en utilisant des fonctionnalités C++ pour éviter ces cas. Vous pouvez utiliser des références, ou vous pouvez simplement dire "passer/utiliser des arguments nuls ou invalides est une erreur" -- une approche qui est également applicable aux conteneurs, tels que les pointeurs intelligents. Un comportement cohérent et correct de plus en plus grand interdit à ces bogues d'aller loin.

de là, vous avez seulement une portée et un contexte très limités où un pointeur nul peut exister (ou est autorisé).

la même chose peut être appliquée aux pointeurs qui ne sont pas const . Suivre la valeur d'un pointeur est trivial parce que sa portée est si petite, et l'utilisation inappropriée est vérifiée et bien définie. Si votre ensemble d'outils et les ingénieurs ne peuvent pas suivre le programme après une lecture rapide ou s'il y a une vérification d'erreur inappropriée ou un flux de programme incohérent/indulgent, vous avez d'autres, plus gros problèmes.

enfin, votre compilateur et l'environnement ont probablement des gardes pour les moments où vous souhaitez introduire des erreurs (gribouillage), détecter les accès à la mémoire libérée, et attraper d'autres UB liés. Vous pouvez également introduire des diagnostics similaires dans vos programmes, souvent sans affecter les programmes existants.

2
répondu justin 2012-02-28 07:43:22

permettez-moi d'étendre ce que vous avez déjà mis dans votre question.

voici ce que vous avez mis dans votre question, sous forme de point de puce:


mettre des pointeurs à zéro après Supprimer n'est pas une bonne pratique universelle en C++. Il y a des moments où:

  • c'est une bonne chose à faire
  • et les fois où il est inutile et peut cacher des erreurs.

Toutefois, il est pas de temps quand c'est mauvais ! Vous allez pas introduire plus de bogues en les annulant explicitement, vous ne serez pas fuite mémoire, vous ne serez pas provoquer un comportement non défini se produire.

donc, en cas de doute, il suffit de l'annuler.

cela dit, si vous sentez que vous devez explicitement null certains pointeur, puis pour moi cela semble comme vous n'avez pas divisé une méthode assez, et devrait regarder l'approche de remaniement appelé "méthode D'extraction" pour diviser la méthode en parties séparées.

1
répondu Lasse Vågsæther Karlsen 2009-12-18 22:57:19

Oui.

le seul" mal " qu'il peut faire est d'introduire l'inefficacité (une opération de stockage inutile) dans votre programme - mais ce frais généraux sera insignifiant par rapport au coût d'allocation et de libération du bloc de mémoire dans la plupart des cas.

Si vous ne le faites pas, vous sera avoir une certaine mauvaise pointeur derefernce bugs un jour.

j'utilise toujours une macro pour supprimer:

#define SAFEDELETE(ptr) { delete(ptr); ptr = NULL; }

(et pareil pour un tableau, free(), libérant des poignées)

vous pouvez également écrire des méthodes" Self delete " qui prennent une référence au pointeur du code appelant, donc ils forcent le pointeur du code appelant à NULL. Par exemple, pour supprimer un sous-arborescence de nombreux objets:

static void TreeItem::DeleteSubtree(TreeItem *&rootObject)
{
    if (rootObject == NULL)
        return;

    rootObject->UnlinkFromParent();

    for (int i = 0; i < numChildren)
       DeleteSubtree(rootObject->child[i]);

    delete rootObject;
    rootObject = NULL;
}

modifier

oui, ces techniques violent certaines règles sur l'utilisation des macros (et oui, ces jours-ci vous pourriez probablement atteindre le même résultat avec des gabarits) - mais en utilisant pendant de nombreuses années I jamais jamais accédé mémoire morte - l'un des plus Nasti, et le plus difficile et le plus long à résoudre les problèmes que vous pouvez faire face. Dans la pratique depuis de nombreuses années, ils ont effectivement éliminé une classe de bogues whjole de chaque équipe que je leur ai présenté.

il y a aussi plusieurs façons de mettre en œuvre ce qui précède - j'essaie juste d'illustrer l'idée de forcer les gens à annuler un pointeur s'ils suppriment un objet, plutôt que de fournir un moyen pour eux de libérer la mémoire qui ne supprime pas le pointeur de l'appelant.

bien sûr, l'exemple ci-dessus n'est qu'un pas vers un pointeur automatique. Ce que je n'ai pas suggéré parce que L'OP s'interrogeait spécifiquement sur le cas de ne pas utiliser de pointeur automatique.

1
répondu Jason Williams 2009-12-19 07:39:34

"Il y a des moments où c'est une bonne chose à faire, et des fois lorsque c'est inutile et peut masquer les erreurs"

je peux voir deux problèmes: Ce code simple:

delete myObj;
myobj = 0

devient à un pour-liner dans un environnement multithread:

lock(myObjMutex); 
delete myObj;
myobj = 0
unlock(myObjMutex);

la "meilleure pratique" de Don Neufeld ne s'applique pas toujours. Par exemple: dans un projet automobile, nous avons dû placer des pointeurs à 0, même dans les destructeurs. Je peux imaginer dans des logiciels critiques ces règles ne sont pas rares. Il est plus facile (et sage) de les suivre, que d'essayer de convaincre l'équipe/code de vérification pour chaque pointeur utiliser dans le code, qu'une ligne micropression ce pointeur est redondant.

un autre danger est d'utiliser cette technique dans les exceptions-en utilisant le code:

try{  
   delete myObj; //exception in destructor
   myObj=0
}
catch
{
   //myObj=0; <- possibly resource-leak
}

if (myObj)
  // use myObj <--undefined behaviour

dans un tel code, soit vous produisez une fuite de ressources et reportez le problème, soit le processus s'écrase.

donc, ces deux problèmes allant spontanément à travers ma tête (Herb Sutter en dira certainement plus), toutes les questions du genre "Comment éviter d'utiliser des pointeurs intelligents et faire le travail en toute sécurité avec des pointeurs normaux" sont obsolètes.

1
répondu Valentin Heinitz 2014-01-27 11:38:52

il y a toujours pointes pendantes à surveiller.

0
répondu GrayWizardx 2009-12-18 22:57:42

si vous allez réattribuer le pointeur avant de l'utiliser à nouveau (le déréférencier, le passer à une fonction, etc.), en faisant le pointeur NULL est juste un supplément de fonctionnement. Cependant, si vous n'êtes pas sûr qu'il sera réattribué ou non avant qu'il ne soit utilisé à nouveau, le définir à NULL est une bonne idée.

Comme beaucoup l'ont dit, il est bien sûr beaucoup plus facile d'utiliser des pointeurs intelligents.

Edit: comme Thomas Matthews a dit dans ce plus tôt réponse , si un pointeur est supprimé dans un destructeur, il n'est pas nécessaire de lui attribuer NULL car il ne sera plus utilisé car l'objet est déjà en cours de destruction.

0
répondu Dustin 2017-05-23 10:31:25

je peux imaginer mettre un pointeur à NULL après l'avoir supprimé étant utile dans de rares cas où il y a un légitime scénario de réutilisation dans une seule fonction (ou objet). Sinon, cela n'a aucun sens - un pointeur doit pointer vers quelque chose de significatif aussi longtemps qu'il existe - période.

0
répondu Nemanja Trifunovic 2009-12-19 17:14:33

si le code n'appartient pas à la partie la plus critique de votre application, Gardez-le simple et utilisez shared_ptr:

shared_ptr<Foo> p(new Foo);
//No more need to call delete

il effectue le comptage de référence et est sans fil. Vous pouvez le trouver dans l'espace de noms tr1 (std::tr1, #include < memory >) ou si votre compilateur ne le fournit pas, récupérez-le de boost.

0
répondu beta4 2009-12-19 19:10:43