Question

Je vais commencer en disant, utiliser des pointeurs intelligents et vous ne serez jamais à vous soucier de cela.

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 upvotes:

  

pointeurs NULL Définition suivante suppression n'est pas une bonne pratique universelle en C ++. Il y a des moments où il est une bonne chose à faire, et des moments où il est inutile et peut cacher des erreurs.

Il y a beaucoup de cas où il ne serait pas utile. Mais dans mon expérience, il ne peut pas nuire. Quelqu'un éclairez-moi.

Était-ce utile?

La solution

Définition d'un pointeur vers 0 (ce qui est « nul » dans la norme C ++, la valeur NULL définie à partir de C est quelque peu différente) permet d'éviter les collisions doubles suppressions.

Considérez 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 des pointeurs supprimés à 0, vous aurez des ennuis si vous faites les suppressions doubles. Un argument contre la mise en pointeurs à 0 après suppression serait que faire juste masques supprimer deux bogues et les laisse unhandled.

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

Enfin, en ce qui concerne la gestion sidenote allocation d'objets, je vous suggère de jeter un oeil à std::unique_ptr pour la propriété stricte / singulier, std::shared_ptr pour la propriété partagée, ou d'une autre puce mise en œuvre du pointeur, en fonction de vos besoins.

Autres conseils

pointeurs NULL à réglage après que vous avez supprimé ce qu'il a à peut certainement pas de mal, mais il est souvent un peu d'un sparadrap sur un problème plus fondamental: Pourquoi utilisez-vous un pointeur en premier lieu? Je vois deux raisons typiques:

  • Vous voulait simplement quelque chose alloué sur le tas. Dans ce cas, l'enveloppant dans un objet RAII aurait été beaucoup plus sûr et plus propre. Mettre fin à la portée de l'objet RAII lorsque vous ne avez plus besoin de l'objet. C'est ainsi std::vector fonctionne, et il résout le problème de laisser accidentellement des pointeurs vers désallouées mémoire autour. Il n'y a pas de pointeurs.
  • Ou peut-être que vous vouliez une sémantique de propriété complexes partagées. Le pointeur de retour new pourrait ne pas être le même que celui qui delete est appelé. Plusieurs objets peuvent avoir utilisé l'objet en même temps 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 autour dans le code utilisateur, vous faites fausse route. Le pointeur ne doit pas être là pour pointer vers les ordures en premier lieu. Pourquoi est-il pas un objet de prendre la responsabilité d'assurer sa validité? Pourquoi ne pas la fin de la portée lorsque le pointu à opposer le fait?

J'ai encore mieux les meilleures pratiques: Lorsque cela est possible, mettre fin à la portée de la variable

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

J'ai toujours mis un pointeur vers NULL (maintenant nullptr) après la suppression de l'objet (s) il pointe.

  1. Il peut aider à attraper de nombreuses références à la mémoire libérée (en supposant que vos défauts de la plate-forme sur un déréférencer un pointeur NULL).

  2. Il ne sera pas attraper toutes les références à la mémoire free'd si, par exemple, vous avez des copies du pointeur se trouvant autour. Mais certains est mieux que rien.

  3. Il masque un double supprimer, mais je trouve ceux qui sont beaucoup moins fréquentes 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 selon lequel il est inutile ne me convainc pas.

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

  6. Il est souvent pratique, lorsque le débogage, pour voir la valeur nulle plutôt que d'un pointeur rassis.

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

Je également définir d'autres types de poignées ressources à la valeur sans ressource lorsque la ressource est free'd (ce qui est généralement seulement dans le destructor d'une enveloppe RAII écrite pour encapsuler la ressource).

Je travaillais sur un grand (9 millions de déclarations) produit commercial (principalement en C). À un moment donné, nous avons utilisé la magie macro null le pointeur lorsque la mémoire a été libéré. Cette exposition immédiatement beaucoup de bugs tapies qui ont été rapidement fixés. Pour autant que je me souvienne, on n'a jamais eu un bug double-free.

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 sera piétiner le pointeur supprimé automatiquement (dans de nombreux cas) si vous compilez avec l'option / SDL.

Tout d'abord, il y a beaucoup de questions existantes sur ce sujet et des sujets étroitement liés, par exemple Pourquoi ne supprime pas défini le pointeur nULL .

Dans votre code, la question ce qui se passe dans (l'utilisation p). Par exemple, si quelque part vous avez du code comme ceci:

Foo * p2 = p;

puis sélectionnez le réglage p NULL très peu accomplit, comme vous avez encore le pointeur p2 à se soucier.

Cela ne veut pas dire que la fixation d'un pointeur NULL est toujours inutile. Par exemple, si p était une variable membre pointant vers une ressource qui est la vie n'a pas été exactement la même que la classe contenant p, puis sélectionnez le réglage p NULL pourrait être un moyen utile d'indiquer la présence ou l'absence de la ressource.

S'il y a plus de code après la delete, Oui. Lorsque le pointeur est supprimé dans un constructeur ou à la fin du procédé ou de la fonction, numéro

Le point de cette parabole est de rappeler au programmeur, au cours de l'exécution, que l'objet a déjà été supprimé.

Une encore meilleure pratique consiste à utiliser Smart Pointers (partagé ou scope) qui suppriment automagiquement leurs objets cibles.

Comme d'autres l'ont dit, delete ptr; ptr = 0; ne va pas causer des démons de sortir de votre nez. Cependant, elle encourage l'utilisation de ptr comme un drapeau de toutes sortes. Le code devient jonché delete et le réglage du pointeur vers NULL. L'étape suivante consiste à disperser if (arg == NULL) return; dans votre code pour protéger contre l'utilisation accidentelle d'un pointeur de NULL. Le problème se produit une fois que les contrôles contre NULL deviennent vos principaux moyens de vérification de l'état d'un objet ou d'un programme.

Je suis sûr qu'il ya une odeur de code à utiliser un pointeur comme un drapeau quelque part, mais je ne l'ai pas trouvé un.

Je vais changer un peu votre question:

  

Voulez-vous utiliser un uninitialized   aiguille? Vous savez, celui que vous ne l'avez pas   ensemble à NULL ou allouer la mémoire il   les points à?

Il existe deux scénarios où le paramètre pointeur NULL peut être sautée:

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

Pendant ce temps, en faisant valoir que la mise en le pointeur NULL peut cacher des erreurs me semble comme faisant valoir que vous ne devriez pas corriger un bug car le correctif pourrait cacher un autre bug. Les seuls bugs qui pourraient montrer si le pointeur n'est pas réglé sur NULL seraient ceux qui tentent d'utiliser le pointeur. Mais à NULL mise provoqueraient en fait exactement le même bug montrerait si vous l'utilisez avec la mémoire libérée, ne serait-il?

Si vous avez pas d'autre contrainte que vous oblige soit ou définissez pas le pointeur NULL après que vous supprimez (une telle contrainte a été mentionnée par Neil Butterworth ), alors ma préférence personnelle est de le laisser être.

Pour moi, la question n'est pas « est-ce une bonne idée? » mais « ce comportement que je prévenir ou permettre de réussir en faisant cela? » Par exemple, si cela permet de voir tout autre code que le pointeur est plus disponible, pourquoi est autre code essayant même de regarder des pointeurs libérés après leur libération? Habituellement, il est un bug.

Il fait aussi plus de travail que nécessaire et entrave le débogage post-mortem. Moins vous touchez la mémoire après que vous ne avez pas besoin, plus il est facile de comprendre pourquoi quelque chose est écrasé. Plusieurs fois, je me suis appuyé sur le fait que la mémoire est dans un état similaire à quand un bug particulier a eu lieu pour diagnostiquer et fixer ledit bug.

supprimer après Explicitement annulant suggère fortement à un lecteur que le pointeur représente quelque chose qui est conceptuellement option . Si je voyais que se fait, je commence à craindre que partout dans la source le pointeur se habitue qu'il devrait être d'abord testés à NULL.

Si c'est ce que vous voulez dire en fait, il est préférable de faire ce explicite dans la source en utilisant quelque chose comme boost :: option

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

Mais si vous les gens voulaient vraiment connaître le pointeur a « mal tourné », je vais la hauteur dans un accord à 100% avec ceux qui disent que la meilleure chose à faire est de faire sortir du champ d'application. Ensuite, vous utilisez le compilateur pour éviter la possibilité de mauvais déréférence lors de l'exécution.

C'est le bébé dans tous les C ++ bathwater, ne devrait pas le jeter. :)

Dans un programme bien structuré avec vérification d'erreur appropriée, il n'y a aucune raison pas pour lui assigner null. 0 est le seul en tant que valeur non valide universellement reconnue dans ce contexte. Fail dur et bientôt tomber en panne.

Un grand nombre des arguments contre l'attribution 0 suggèrent que peut cacher un bug ou compliquer le flux de contrôle. Au fond, qui est soit une erreur en amont (pas de votre faute (désolé pour le mauvais jeu de mots)) ou d'une autre erreur sur le 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 une valeur spéciale et écrire tous les esquives nécessaires autour de ce qui est une complication qu'ils ont délibérément mis en place. La meilleure est la mise en quarantaine, plus vite vous trouverez les cas d'abus, et moins ils sont en mesure de se propager dans d'autres programmes.

Bien des programmes structurés peuvent être conçus en utilisant des fonctionnalités de C ++ pour éviter ces cas. Vous pouvez utiliser des références, ou vous pouvez simplement dire « passer / en utilisant des arguments nuls ou invalides est une erreur » - une approche qui est également applicable aux conteneurs, tels que les pointeurs intelligents. L'augmentation de comportement correct et cohérent interdit ces bugs d'obtenir beaucoup.

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

La même chose peut être appliqué à des pointeurs qui ne sont pas const. Suite à la valeur d'un pointeur est trivial parce que sa portée est si petit, et une mauvaise utilisation est vérifiée et bien définie. Si votre jeu d'outils et les ingénieurs ne peuvent pas suivre le programme suivant une lecture rapide ou il y a la vérification des erreurs inappropriées ou le déroulement du programme incohérent / clément, vous avez d'autres problèmes plus graves.

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

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

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


pointeurs NULL Définition suivante suppression n'est pas une bonne pratique universelle en C ++. Il y a des moments où:

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

Cependant, il y a pas de temps lorsque cela est mauvais ! Vous pas introduire plus de bugs par explicitement annulant, vous ne serez pas fuite mémoire, vous ne provoquer un comportement non défini pour arriver.

Ainsi, en cas de doute, juste null il.

Cela dit, si vous sentez que vous avez à null explicitement un certain pointeur, puis me cela sonne comme vous ne l'avez pas diviser assez une méthode, et devrait se pencher sur l'approche de refactoring appelée « méthode d'extraction » de diviser le procédé dans des parties séparées.

Oui.

Le seul « préjudice » qu'il peut faire est d'introduire l'inefficacité (une opération de stockage inutile) dans votre programme - mais cette surcharge sera négligeable par rapport au coût de l'allocation et de libérer le bloc de mémoire dans la plupart des cas

Si vous ne le faites pas, vous ont quelques bugs de derefernce pointeur méchant un jour.

J'utilise toujours une macro pour suppression:

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

(et similaire pour un réseau, d'une connexion (), en libérant les poignées)

Vous pouvez également écrire « auto supprimer » des méthodes qui prennent une référence au pointeur du code d'appel, ils forcer le pointeur de code appelant à NULL. Par exemple, pour supprimer une 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 ne violent certaines règles relatives à l'utilisation des macros (et oui, ces jours-ci vous pourriez probablement obtenir le même résultat avec des modèles) - mais en utilisant depuis de nombreuses années, je jamais accédés mort mémoire - l'un des plus méchant et le plus difficile et le plus de temps à des problèmes de débogage que vous pouvez faire face. En pratique depuis de nombreuses années, ils ont éliminé une classe whjole de bugs de chaque équipe que je vous ai présenté les sur.

Il y a aussi plusieurs façons que vous pouvez mettre en œuvre ci-dessus - je suis juste essayer d'illustrer l'idée de forcer les gens à NULL un pointeur s'ils suppriment un objet, plutôt que de fournir un moyen pour eux de libérer la mémoire qui ne fonctionne pas NULL le pointeur de l'appelant.

Bien sûr, l'exemple ci-dessus est juste une étape vers un pointeur automatique. Ce que je ne suggère pas que l'OP demandait spécifiquement sur le cas de ne pas utiliser un pointeur automatique.

« Il y a des moments où il est une bonne chose à faire, et des moments où il est inutile et peut cacher des erreurs »

Je vois deux problèmes: Ce code simple:

delete myObj;
myobj = 0

devient à un pour-liner en environnement multithread:

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

La « meilleure pratique » de Don Neufeld ne sont pas toujours applicables. Par exemple. dans un projet automobile, nous avons dû mettre des pointeurs vers 0 même dans Destructeurs. Je peux imaginer dans les logiciels de sécurité critiques telles règles ne sont pas rares. Il est plus facile (et sage) de les suivre que d'essayer de persuader l'équipe / code-vérificateur pour chaque utilisation du pointeur dans le code, qu'une mise à zéro de la ligne ce pointeur est redondant.

Un autre danger se fonde sur cette technique dans les exceptions utilisant le code suivant:

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

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

Dans ce code, soit vous produisez des fuites de ressources et de reporter le problème ou les processus se bloque.

Alors, ces deux problèmes vont spontanément dans ma tête (Herb Sutter serait sûr en dire plus) faire pour moi toutes les questions du genre « Comment éviter d'utiliser des smart-pointeurs et faire le travail en toute sécurité avec des pointeurs normaux » comme obsolète .

Il y a toujours Dangling Pointeurs à se soucier.

Si vous allez réattribuer le pointeur avant de l'utiliser à nouveau (il déréférencement, en passant à une fonction, etc.), ce qui rend le NULL pointeur est juste une opération supplémentaire. Toutefois, si vous n'êtes pas sûr que ce sera réaffecté ou non avant qu'il ne soit utilisé à nouveau, à NULL la mise en est une bonne idée.

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

Edit: Comme l'a dit Thomas Matthews dans cette réponse précédente , si un pointeur est supprimé dans un destructor, il n'y a pas besoin d'attribuer NULL à lui car il ne sera pas utilisé à nouveau parce que l'objet est détruit déjà.

Je peux imaginer établir un pointeur NULL après la suppression étant utile dans les rares cas où il y a un légitime scénario de la réutilisation dans une fonction unique (ou objet). Dans le cas contraire, il ne fait aucun sens - un pointeur doit pointer vers quelque chose de significatif tant qu'il existe -. Period

Si le code ne fait pas partie de la plupart des performances critiques de votre application, gardez-le simple et utiliser un shared_ptr:

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

Il effectue le comptage de référence et est thread-safe. Vous pouvez le trouver dans le TR1 (std :: espace de noms TR1, #include ) ou si votre compilateur ne fournit pas, obtenir de boost.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top