Est-il recommandé de NULL un pointeur après l'avoir supprimé?


150

Je commencerai par dire, utilisez des pointeurs intelligents et vous n'aurez jamais à vous en soucier.

Quels sont les problèmes avec le code suivant?

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

Cela a été déclenché par une réponse et des commentaires à une autre question. Un commentaire de Neil Butterworth a généré quelques votes positifs:

Définir des pointeurs sur NULL après la suppression n'est pas une bonne pratique universelle en C ++. Il y a des moments où c'est une bonne chose à faire et des moments où cela ne sert à rien et peut cacher des erreurs.

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


6
@Andre: Techniquement, c'est indéfini. Il est probable que vous ayez accès à la même mémoire qu'avant, mais elle peut maintenant être utilisée par autre chose. Si vous supprimez de la mémoire deux fois, cela risque de gâcher l'exécution de votre programme d'une manière difficile à trouver. Il est cependant sans danger pour deleteun pointeur nul, ce qui est une des raisons pour lesquelles la mise à zéro d'un pointeur peut être bonne.
David Thornley

5
@ André Pena, c'est indéfini. Souvent, ce n'est même pas reproductible. Vous définissez le pointeur sur NULL pour rendre l'erreur plus visible lors du débogage et peut-être pour la rendre plus répétable.
Mark Ransom

3
@ André: Personne ne sait. C'est un comportement indéfini. Il peut se bloquer avec une violation d'accès ou il peut remplacer la mémoire utilisée par le reste de l'application. La norme de langage ne donne aucune garantie de ce qui se passe et vous ne pouvez donc pas faire confiance à votre application une fois que cela s'est produit. Il aurait pu tirer les missiles nucléaires ou formater votre disque dur. cela peut corrompre la mémoire de votre application ou faire sortir des démons de votre nez. Tous les paris sont levés.
jalf

16
Les démons volants sont une fonctionnalité, pas un bug.
jball

3
Cette question n'est pas un doublon car l'autre question concerne C et celle-ci concerne C ++. Beaucoup de réponses reposent sur des éléments tels que les pointeurs intelligents, qui ne sont pas disponibles en C ++.
Adrian McCarthy

Réponses:


86

Mettre un pointeur à 0 (qui est "nul" en C ++ standard, la définition NULL à partir de C est quelque peu différente) évite les plantages lors de doubles suppressions.

Considérer ce qui suit:

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

Tandis 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 sur 0, vous aurez des problèmes si vous effectuez des doubles suppressions. Un argument contre la mise à 0 des pointeurs après la suppression serait que cela masque simplement les bogues de double suppression et les laisse non gérés.

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

Enfin, une remarque concernant la gestion de l'allocation d'objets, je vous suggère de jeter un œil à la std::unique_ptrpropriété stricte / singulière, std::shared_ptrà la propriété partagée ou à une autre implémentation de pointeur intelligent, en fonction de vos besoins.


13
Votre application ne plantera pas toujours lors d'une double suppression. Selon ce qui se passe entre les deux suppressions, tout peut arriver. Très probablement, vous corromprez votre tas et vous vous planterez plus tard dans un morceau de code totalement indépendant. Bien qu'un segfault soit généralement meilleur que d'ignorer silencieusement l'erreur, le segfault n'est pas garanti dans ce cas, et son utilité est discutable.
Adam Rosenfield

28
Le problème ici est le fait que vous avez une double suppression. Rendre le pointeur NULL masque simplement le fait qu'il ne le corrige pas et ne le rend pas plus sûr. Imaginez qu'un mainainer revienne un an plus tard et voit foo supprimé. Il pense maintenant qu'il peut réutiliser le pointeur, malheureusement, il peut manquer la deuxième suppression (il peut même ne pas être dans la même fonction) et maintenant la réutilisation du pointeur est maintenant détruite par la deuxième suppression. Tout accès après la deuxième suppression est désormais un problème majeur.
Martin York

11
Il est vrai que le réglage du pointeur NULLpeut masquer un bogue de double suppression. (Certains pourraient considérer que ce masque est en fait une solution - c'est, mais pas une très bonne car il n'atteint pas la racine du problème.) Mais ne pas le définir sur NULL masque loin (FAR!) Plus problèmes courants d'accès aux données après leur suppression.
Adrian McCarthy

AFAIK, std :: auto_ptr est obsolète dans le prochain standard c ++
rafak

Je ne dirais pas obsolète, cela donne l'impression que l'idée est simplement partie. Au contraire, il est remplacé par unique_ptr, qui fait ce que l'on a auto_ptressayé de faire, par une sémantique de déplacement.
GManNickG

55

Définir des pointeurs sur NULL après avoir supprimé ce qu'il a indiqué ne peut certainement pas faire de mal, mais c'est souvent un peu 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 quelque chose 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'en avez plus besoin. C'est ainsi que cela std::vectorfonctionne, et cela résout le problème de laisser accidentellement des pointeurs vers la mémoire désallouée. Il n'y a pas de pointeurs.
  • Ou peut-être souhaitiez-vous une sémantique complexe de la propriété partagée. Le pointeur renvoyé denew peut ne pas être le même que celui qui deleteest 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 doit pas être là pour pointer vers les ordures en premier lieu. Pourquoi n'y a-t-il pas un objet qui assume la responsabilité de garantir sa validité? Pourquoi sa portée ne se termine-t-elle pas lorsque l'objet pointé se termine?


15
Donc, vous faites valoir qu'il n'aurait pas dû y avoir de pointeur brut en premier lieu, et que tout ce qui implique ledit pointeur ne devrait pas être béni avec le terme «bonne pratique»? C'est suffisant.
Mark Ransom

7
Eh bien, plus ou moins. Je ne dirais pas que rien impliquant un pointeur brut ne peut être qualifié de bonne pratique. Juste que c'est l'exception plutôt que la règle. Habituellement, la présence du pointeur indique qu'il y a quelque chose qui ne va pas à un niveau plus profond.
jalf

3
mais pour répondre à la question immédiate, non, je ne vois pas comment la définition de pointeurs sur null peut jamais provoquer des erreurs.
jalf

7
Je ne suis pas d'accord - il y a des cas où un pointeur est bon à utiliser. Par exemple, il y a 2 variables sur la pile et vous voulez en choisir une. Ou vous souhaitez passer une variable facultative à une fonction. Je dirais que vous ne devriez jamais utiliser un pointeur brut en conjonction avec new.
rlbond

4
quand un pointeur est hors de portée, je ne vois pas comment quoi que ce soit ou quiconque pourrait avoir besoin de le gérer.
jalf

43

J'ai une meilleure pratique encore meilleure: si possible, mettez fin à la portée de la variable!

{
    Foo* pFoo = new Foo;
    // use pFoo
    delete pFoo;
}

17
Oui, RAII est votre ami. Enveloppez-le dans une classe et cela devient encore plus simple. Ou ne gérez pas du tout la mémoire vous-même en utilisant la STL!
Brian

24
Oui en effet, c'est la meilleure option. Mais ne répond pas à la question.
Mark Ransom

3
Cela semble être juste un sous-produit de l'utilisation de la période des portées de fonction, et ne résout pas vraiment le problème. Lorsque vous utilisez des pointeurs, vous en transmettez généralement des copies de plusieurs couches de profondeur, puis votre méthode n'a vraiment aucun sens dans le but de résoudre le problème. Bien que je convienne qu'une bonne conception vous aidera à isoler les erreurs, je ne pense pas que votre méthode soit le principal moyen d'y parvenir.
San Jacinto

2
En y réfléchissant bien, si vous pouviez faire cela, pourquoi ne pas oublier le tas et retirer toute votre mémoire de la pile?
San Jacinto

4
Mon exemple est intentionnellement minimal. Par exemple, au lieu de nouveau, peut-être que l'objet est créé par une fabrique, auquel cas il ne peut pas aller sur la pile. Ou peut-être qu'il n'est pas créé au début de la portée, mais situé dans une structure. Ce que j'illustre, c'est que cette approche détectera toute utilisation abusive du pointeur au moment de la compilation , alors que NULLing elle trouvera toute utilisation abusive au moment de l'exécution .
Don Neufeld

31

Je place toujours un pointeur sur NULL(maintenant nullptr) après avoir supprimé le ou les objets vers lesquels il pointe.

  1. Cela peut aider à attraper de nombreuses références à la mémoire libérée (en supposant que votre plate-forme soit défaillante 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 qui traînent. Mais certains valent mieux que rien.

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

  4. Dans de nombreux cas, le compilateur va l'optimiser. Donc, l'argument selon lequel ce n'est pas nécessaire ne me convainc pas.

  5. Si vous utilisez déjà RAII, il n'y a pas beaucoup de deletes dans votre code pour commencer, donc l'argument selon lequel l'affectation supplémentaire cause du désordre ne me convainc pas.

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

  7. Si cela vous dérange toujours, utilisez plutôt un pointeur intelligent ou une référence.

J'ai également défini d'autres types de descripteurs de ressources sur la valeur sans ressource lorsque la ressource est libre (qui est généralement uniquement dans le destructeur d'un wrapper RAII écrit pour encapsuler la ressource).

J'ai travaillé sur un grand produit commercial (9 millions de relevés) (principalement en C). À un moment donné, nous avons utilisé la magie des macros pour annuler le pointeur lorsque la mémoire était libérée. Cela a immédiatement exposé de nombreux bugs cachés qui ont été rapidement corrigés. Autant que je me souvienne, nous n'avons jamais eu de bogue sans double.

Mise à jour: Microsoft estime que c'est une bonne pratique pour la sécurité et recommande cette pratique dans ses stratégies SDL. Apparemment, MSVC ++ 11 écrasera automatiquement le pointeur supprimé (dans de nombreuses circonstances) si vous compilez avec l'option / SDL.


12

Premièrement, il existe de nombreuses questions sur ce sujet et sur des sujets étroitement liés, par exemple Pourquoi la suppression ne définit-elle pas le pointeur sur NULL? .

Dans votre code, le problème de ce qui se passe (utilisez p). Par exemple, si quelque part vous avez du code comme celui-ci:

Foo * p2 = p;

puis définir p sur NULL ne fait pas grand-chose, car vous avez toujours à vous soucier du pointeur p2.

Cela ne veut pas dire que définir un pointeur sur 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, définir p sur NULL pourrait être un moyen utile d'indiquer la présence ou l'absence de la ressource.


1
Je suis d'accord qu'il y a des moments où cela n'aidera pas, mais vous semblez laisser entendre que cela pourrait être activement nuisible. Était-ce votre intention ou ai-je mal lu?
Mark Ransom

1
La question de savoir s'il existe une copie du pointeur n'est pas pertinente pour la question de savoir si la variable du pointeur doit être définie sur NULL. Le définir sur NULL est une bonne pratique pour les mêmes raisons que le nettoyage de la vaisselle après le dîner est une bonne pratique - bien que ce ne soit pas une garantie contre tous les bogues qu'un code peut avoir, cela favorise une bonne santé du code.
Franci Penov

2
@Franci Beaucoup de gens semblent être en désaccord avec vous. Et s'il existe une copie est certainement pertinent si vous essayez d'utiliser la copie après avoir supprimé l'original.

3
Franci, il y a une différence. Vous nettoyez la vaisselle parce que vous la réutilisez. Vous n'avez pas besoin du pointeur après l'avoir supprimé. Ce devrait être la dernière chose que vous faites. La meilleure pratique consiste à éviter complètement la situation.
GManNickG

1
Vous pouvez réutiliser une variable, mais ce n'est plus un cas de programmation défensive; c'est ainsi que vous avez conçu la solution au problème actuel. L'OP discute de la question de savoir si ce style défensif est quelque chose que nous devrions rechercher, et non pas que nous ne mettions jamais un pointeur sur null. Et idéalement, à votre question, oui! N'utilisez pas de pointeurs après les avoir supprimés!
GManNickG

7

S'il y a plus de code après le delete, Oui. Lorsque le pointeur est supprimé dans un constructeur ou à la fin d'une méthode ou d'une 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 étendus) qui suppriment automatiquement leurs objets cibles.


Tout le monde (y compris le questionneur original) convient que les pointeurs intelligents sont la voie à suivre. Le code évolue. Il se peut qu'il n'y ait pas plus de code après la suppression lorsque vous la corrigez pour la première fois, mais cela est susceptible de changer avec le temps. Mettre dans la mission aide lorsque cela se produit (et ne coûte presque rien dans l'intervalle).
Adrian McCarthy

3

Comme d'autres l'ont dit, cela delete ptr; ptr = 0;ne fera pas sortir les démons de votre nez. Cependant, cela encourage l'utilisation de en ptrtant que drapeau. Le code est jonché de deleteet positionnant le pointeur sur NULL. L'étape suivante consiste à disperser if (arg == NULL) return;votre code pour vous protéger contre l'utilisation accidentelle d'un NULLpointeur. Le problème se produit une fois que les vérifications NULLdeviennent 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 pointeur comme indicateur quelque part, mais je n'en ai pas trouvé.


9
Il n'y a rien de mal à utiliser un pointeur comme indicateur. Si vous utilisez un pointeur et que ce NULLn'est pas une valeur valide, vous devriez probablement utiliser une référence à la place.
Adrian McCarthy

2

Je vais modifier légèrement votre question:

Utiliseriez-vous un pointeur non initialisé? Vous savez, celui que vous n'avez pas défini sur NULL ou alloué la mémoire vers laquelle il pointe?

Il existe deux scénarios dans lesquels la définition du pointeur sur NULL peut être ignorée:

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

Pendant ce temps, dire que définir le pointeur sur NULL pourrait me cacher des erreurs revient à dire que vous ne devriez pas corriger un bogue car le correctif pourrait masquer un autre bogue. Les seuls bogues qui pourraient montrer si le pointeur n'est pas défini sur NULL sont ceux qui essaient d'utiliser le pointeur. Mais le définir sur NULL provoquerait en fait exactement le même bogue que celui indiqué si vous l'utilisiez avec de la mémoire libérée, n'est-ce pas?


(A) "sonne comme argumenter que vous ne devriez pas corriger un bogue" Ne pas définir un pointeur sur NULL n'est pas un bogue. (B) "Mais le définir sur NULL provoquerait en fait exactement le même bogue" Non. Le réglage sur NULL masque la double suppression . (C) Résumé: la définition de NULL masque la double suppression, mais expose les références périmées. Ne pas définir NULL peut masquer les références périmées, mais expose les doubles suppressions. Les deux parties conviennent que le vrai problème est de corriger les références périmées et les doubles suppressions.
Mooing Duck

2

Si vous n'avez aucune autre contrainte qui vous oblige à définir ou à ne pas définir le pointeur sur NULL après l'avoir supprimé (une telle contrainte a été mentionnée par Neil Butterworth ), ma préférence personnelle est de la laisser telle quelle.

Pour moi, la question n'est pas "est-ce une bonne idée?" mais "quel comportement empêcherais-je ou autoriserais-je à réussir en faisant cela?" Par exemple, si cela permet à un autre code de voir que le pointeur n'est plus disponible, pourquoi un autre code tente-t-il même de regarder les pointeurs libérés après leur libération? Habituellement, c'est un bug.

Il fait également plus de travail que nécessaire et empêche le débogage post-mortem. Moins vous touchez la mémoire après n'en avoir pas besoin, plus il est facile de comprendre pourquoi quelque chose s'est écrasé. Plusieurs fois, je me suis appuyé sur le fait que la mémoire est dans un état similaire à celui d'un bogue particulier pour diagnostiquer et corriger ledit bogue.


2

Annulation explicite après suppression suggère fortement au lecteur que le pointeur représente quelque chose qui est conceptuellement facultatif . Si je voyais cela se faire, je commencerais à m'inquiéter du fait que partout dans la source, le pointeur est utilisé pour qu'il soit d'abord testé contre NULL.

Si c'est ce que vous voulez dire, il vaut mieux rendre cela explicite dans la source en utilisant quelque chose comme boost :: facultatif

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 serai d'accord à 100% avec ceux qui disent que la meilleure chose à faire est de le faire sortir du champ d'application. Ensuite, vous utilisez le compilateur pour éviter la possibilité de mauvaises déréférences au moment de l'exécution.

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


2

Dans un programme bien structuré avec une vérification d'erreur appropriée, il n'y a aucune raison de ne pas lui attribuer la valeur null.0est la seule valeur invalide universellement reconnue dans ce contexte. Échouer dur et échouer bientôt.

De nombreux arguments contre l'attribution 0suggèrent que cela pourrait masquer un bogue ou compliquer le flux de contrôle. Fondamentalement, c'est soit une erreur en amont (pas votre faute (désolé pour le mauvais jeu de mots)) soit une autre erreur de la part du programmeur - peut-être même une indication que le flux du 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'il a délibérément introduite. Plus la quarantaine est bonne, plus vite vous trouvez des cas d'abus et moins ils sont capables de se propager dans d'autres programmes.

Des programmes bien structurés peuvent être conçus à l'aide 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 non valides est une erreur" - une approche qui s'applique également aux conteneurs, tels que les pointeurs intelligents. Augmenter le comportement cohérent et correct empêche ces bogues d'aller loin.

À partir de là, vous n'avez qu'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 le sont pas const. Suivre la valeur d'un pointeur est trivial car sa portée est si petite et une utilisation incorrecte est vérifiée et bien définie. Si votre ensemble d'outils et vos 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 problèmes plus importants.

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


1

Permettez-moi de développer ce que vous avez déjà mis dans votre question.

Voici ce que vous avez mis dans votre question, sous forme de puces:


Définir des pointeurs sur NULL après la suppression n'est pas une bonne pratique universelle en C ++. Il y a des moments où:

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

Cependant, il n'y a pas de moments où c'est mauvais ! Vous n'introduirez pas plus de bogues en l'annulant explicitement, vous ne fuirez pas de mémoire, vous ne provoquerez pas de comportement indéfini .

Donc, en cas de doute, annulez-le.

Cela dit, si vous sentez que vous devez annuler explicitement un pointeur, cela me semble que vous n'avez pas assez divisé une méthode et que vous devriez regarder l'approche de refactoring appelée "méthode d'extraction" pour diviser la méthode en parties séparées.


Je ne suis pas d'accord avec «il n'y a pas de moments où c'est mauvais». Considérez la quantité de coupeur que cet idiome introduit. Vous avez un en-tête inclus dans chaque unité qui supprime quelque chose, et tous ces emplacements de suppression deviennent un peu moins simples.
GManNickG

Il y a des moments où c'est mauvais. Si quelqu'un essaie de déréférencer votre pointeur supprimé maintenant nul alors qu'il ne le devrait pas, il ne plantera probablement pas et ce bogue est «caché». S'ils déréférencent votre pointeur supprimé qui a encore une valeur aléatoire, vous le remarquerez probablement et le bogue sera plus facile à voir.
Carson Myers

@Carson: Mon expérience est tout à fait le contraire: le déréférencement d'un nullptr fera presque tous planter l'application et peut être intercepté par un débogueur. Déréférencer un pointeur suspendu ne produit généralement pas de problème tout de suite, mais conduira souvent à des résultats incorrects ou à d'autres erreurs sur toute la ligne.
MikeMB

@MikeMB Je suis tout à fait d'accord, mon point de vue à ce sujet a considérablement changé au cours des ~ 6,5 dernières années
Carson Myers

En termes de programmeur, nous étions tous quelqu'un d'autre il y a 6-7 ans :) Je ne suis même pas sûr que j'aurais osé répondre à une question C / C ++ aujourd'hui :)
Lasse V. Karlsen

1

Oui.

Le seul «mal» qu'il peut faire est d'introduire de l'inefficacité (une opération de stockage inutile) dans votre programme - mais cette surcharge sera insignifiante 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 aurez un jour de mauvais bugs de déréférencement de pointeurs.

J'utilise toujours une macro pour supprimer:

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

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

Vous pouvez également écrire des méthodes de «suppression automatique» qui prennent une référence au pointeur du code appelant, afin qu'elles forcent le pointeur du code appelant à NULL. Par exemple, pour supprimer un sous-arbre 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;
}

Éditer

Oui, ces techniques violent certaines règles sur l'utilisation des macros (et oui, de nos jours, vous pourriez probablement obtenir le même résultat avec des modèles) - mais en utilisant pendant de nombreuses années, je n'ai jamais accédé à la mémoire morte - l'une des plus méchantes et des plus difficiles et le plus de temps pour déboguer les problèmes auxquels vous pouvez faire face. En pratique, pendant de nombreuses années, ils ont effectivement éliminé toute une classe de bogues de chaque équipe dans laquelle je les ai présentés.

Il existe également de nombreuses façons de mettre en œuvre ce qui précède - j'essaie simplement d'illustrer l'idée de forcer les gens à NULL un pointeur s'ils suppriment un objet, plutôt que de leur fournir un moyen de libérer la mémoire qui ne NULL 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 posait spécifiquement des questions sur le cas de ne pas utiliser de pointeur automatique.


2
Les macros sont une mauvaise idée, en particulier lorsqu'elles ressemblent à des fonctions normales. Si vous souhaitez faire cela, utilisez une fonction basée sur un modèle.

2
Wow ... Je n'ai jamais rien vu de tel anObject->Delete(anObject)invalider le anObjectpointeur. C'est juste effrayant. Vous devez créer une méthode statique pour cela afin que vous soyez obligé de faire TreeItem::Delete(anObject)au minimum.
D.Shawley

Désolé, saisissez-le en tant que fonction plutôt que d'utiliser le formulaire «ceci est une macro» en majuscules. Corrigée. A également ajouté un commentaire pour mieux m'expliquer.
Jason Williams

Et vous avez raison, mon exemple rapidement dénigré était nul! Corrigé :-). J'essayais simplement de penser à un exemple rapide pour illustrer cette idée: tout code qui supprime un pointeur doit garantir que le pointeur est défini sur NULL, même si quelqu'un d'autre (l'appelant) possède ce pointeur. Donc, transmettez toujours une référence au pointeur afin qu'il puisse être forcé à NULL au moment de la suppression.
Jason Williams

1

"Il y a des moments où c'est une bonne chose à faire, et des moments où cela ne sert à rien et peut cacher des erreurs"

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

delete myObj;
myobj = 0

devient un for-liner dans un environnement multithread:

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

Les «meilleures pratiques» de Don Neufeld ne s'appliquent pas toujours. Par exemple, dans un projet automobile, nous avons dû mettre les pointeurs à 0, même dans les destructeurs. J'imagine que dans les logiciels critiques pour la sécurité, de telles règles ne sont pas rares. Il est plus facile (et sage) de les suivre que d'essayer de persuader l'équipe / le vérificateur de code pour chaque utilisation de pointeur dans le code, qu'une ligne annulant ce pointeur est redondante.

Un autre danger est de s'appuyer sur cette technique dans le code utilisant des exceptions:

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 se bloque.

Donc, ces deux problèmes qui me traversent spontanément la tête (Herb Sutter en dirait certainement plus) font pour moi toutes les questions du genre «Comment éviter d'utiliser des pointeurs intelligents et faire le travail en toute sécurité avec des pointeurs normaux» comme obsolètes.


Je ne vois pas comment le 4-liner est nettement plus complexe qu'un 3-liner (il faut quand même utiliser lock_guards) et si votre destructeur vous lance, vous avez de toute façon des ennuis.
MikeMB

Quand j'ai vu cette réponse pour la première fois, je ne comprenais pas pourquoi vous voudriez annuler un pointeur dans un destructeur, mais maintenant je le fais - c'est pour le cas où l'objet propriétaire du pointeur est utilisé après sa suppression!
Mark Ransom


0

Si vous allez réallouer le pointeur avant de l'utiliser à nouveau (le déréférencer, le passer à une fonction, etc.), rendre le pointeur NULL est juste une opération supplémentaire. Cependant, si vous ne savez pas s'il sera réalloué ou non avant d'être réutilisé, le définir sur NULL est une bonne idée.

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

Edit: Comme Thomas Matthews l'a dit dans cette réponse précédente , si un pointeur est supprimé dans un destructeur, il n'est pas nécessaire de lui affecter NULL car il ne sera plus utilisé car l'objet est déjà détruit.


0

Je peux imaginer définir un pointeur sur NULL après sa suppression, ce qui est utile dans de rares cas où il existe un scénario légitime de réutilisation dans une seule fonction (ou objet). Sinon, cela n'a aucun sens - un pointeur doit pointer vers quelque chose de significatif tant qu'il existe - point final.


0

Si le code n'appartient pas à la partie la plus critique des performances de votre application, restez simple et utilisez un shared_ptr:

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

Il effectue le comptage des références et est thread-safe. Vous pouvez le trouver dans le tr1 (espace de noms std :: tr1, #include <mémoire>) ou si votre compilateur ne le fournit pas, récupérez-le à partir de boost.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.