2010-08-18 2 views
11

J'ai lu A case against FreeAndNil mais je ne comprends toujours pas pourquoi je ne peux pas utiliser cette méthode dans un destructeur de classes? Quelqu'un peut-il expliquer.Pourquoi devrais-je utiliser Free et non FreeAndNil dans un destructeur?

Mise à jour: Je pense que le commentaire de Eric Grange était le plus utile pour moi. Le lien montre que ce n'est pas évident comment y faire face et c'est surtout une question de goût. Aussi la méthode FreeAndInvalidate était utile.

+0

Vous avez posé une question bien ici. – Ampere

Répondre

14

Le problème qui est que beaucoup semblent utiliser FreeAndNil comme la magie balle qui tuera ce dragon mystérieux crash . Si vous utilisez FreeAndNil() dans le destructeur semble résoudre un crash ou d'autres problèmes de corruption de mémoire, alors vous devriez creuser plus profondément dans la cause réelle. Quand je vois ceci, la première question que je pose est: pourquoi le champ d'instance est-il accédé après cette instance a été détruite? Que pointe généralement vers un problème de conception.

Il soutient qu'il cache le vrai problème que vous avez. Cela doit signifier que votre code accède aux propriétés/champs/méthodes d'un objet déjà détruit (le destructeur est appelé). Donc, au lieu de cacher le vrai problème avec FreeAndNil, vous devriez vraiment résoudre le problème sous-jacent.

Ce code ci-dessous ne planterait pas si vous utilisiez FreeAndNil PropertyA dans le destructeur de SomeObject. Mais il cache le vrai problème que SomeObject est utilisé après qu'il est détruit. Il est préférable de résoudre ce problème de conception (accès aux objets détruits) au lieu de le cacher.

SomeObject.Free; // Destructor called 
if Assigned(SomeObject.PropertyA) then // SomeObject is destroyed, but still used 
    SomeObject.PropertyA.Method1; 

EDIT

Sur l'autre cas, on pourrait dire que si FreeAndNil n'est pas utilisé, le code ne plantera pas non plus. Puisque même si l'objet est détruit, la mémoire pourrait ne pas être réutilisée et toutes les structures pourraient être intactes. Le code ci-dessus pourrait même fonctionner sans problème si Free est utilisé pour détruire PropertyA au lieu de FreeAndNil. Et si FreeAndNil était utilisé pour détruire SomeObject, vous verriez également le vrai problème quel que soit le code dans le destructeur. Donc, même si je suis d'accord avec l'argument qu'il pourrait cacher le vrai défaut de conception et n'utiliser personnellement pas FreeAndNil dans les destructeurs, ce n'est pas une solution miracle pour découvrir de tels défauts de conception.

+0

Le problème que n'utilise pas FreeAndNIL ignore est si votre destructeur rencontre une exception, laissant un objet non détruit qui pourrait être sujet à une autre tentative de détruire. Dans ce cas, les objets déjà Free'd ne devraient bien sûr pas être à nouveau libres. FreeAndNIL protège contre les destructeurs incomplets. Il y a un argument qu'un destructeur incomplet reflète aussi un mauvais design, mais malheureusement vous ne contrôlez pas toujours ce qui se passera dans vos destructeurs (par exemple si vous détruisez d'autres objets dont vous ne contrôlez pas le comportement destructeur) – Deltics

+4

Pour la protection, vous pouvez utiliser quelque chose comme 'FreeAndInvalidate' (http://delphitools.info/2010/02/06/dont-abuse-freeandnil-anymore/) pour rendre la référence invalide et pas juste nulle, de sorte que toutes les autres tentatives accéder à l'objet déclenchera une exception. –

+0

Merci pour le lien. Cela me donne un aperçu. –

7

Le problème est assez facile à expliquer, et la controverse autour de ce problème est plus subjective que objective. L'utilisation de FreeAndNil est tout simplement inutile si la variable référence à l'objet libéré sera hors de portée:

procedure Test; 
var 
    LObj: TObject; 
begin 
    LObj := TObject.Create; 
    try 
    {...Do work...} 
    finally 
    //The LObj variable is going out of scope here, 
    // so don't bother nilling it. No other code can access LObj. 

    //FreeAndNil(LObj); 
    LObj.Free; 
    end; 
end; 

Dans l'extrait de code ci-dessus, la variable nilling serait inutile, LObj pour la raison donnée. Cependant, si une variable d'objet peut être instanciée et libérée plusieurs fois pendant la durée de vie d'une application, il devient alors nécessaire de vérifier si l'objet est effectivement instancié ou non. Le moyen le plus simple de vérifier cela est de savoir si la référence d'objet a été définie sur nil. Afin de faciliter ce paramètre à nil, la méthode FreeAndNil() libèrera les ressources et définira nil pour vous.Ensuite, dans le code, vous pouvez vérifier si l'objet est instancié avec LObj = nil ou Assigned(LObj).

Le cas est une zone grise, mais pour la plupart, .Free devrait être en sécurité, et nilling les références aux sous-objets dans le destructor devrait pas être nécessaire si vous souhaitez utiliser .Free ou FreeAndNil() objet dans Destructeurs. Il existe différents arguments sur la façon de traiter les exceptions dans les constructeurs et les destructeurs.

Maintenant, faites attention: si vous préférez choisir d'utiliser ou non .Free ou FreeAndNil() en fonction des circonstances spécifiques décrites ci-dessus, c'est très bien, mais il faut noter que le coût d'un bug à cause pas nilling une référence d'objet libéré qui est ensuite consulté peut être très élevé. Si le pointeur est ensuite accédé (objet libéré mais référence non définie), il peut arriver que vous soyez malchanceux et que la détection de la corruption de mémoire se passe de nombreuses lignes de code loin de l'accès à la référence d'objet libérée mais non indiquée. Ce genre de bug peut prendre beaucoup de temps à réparer, et oui, je sais comment utiliser FastMM.

Par conséquent pour certaines personnes, y compris moi, il est devenu une habitude (un paresseux, peut-être) de simplement nier tous les pointeurs d'objet quand ils sont libérés, même lorsque le nettoyage n'est pas strictement nécessaire.

+0

Vous traitez un seul cas où vous avez une minuscule procédure dans laquelle la variable est hors de portée. Il y a plus que ça. Mais +1, je suis d'accord avec FreeAndNil. – Ampere

+0

@Altar: J'ai choisi cet exemple car il fournit la meilleure situation dans laquelle on peut affirmer que "Free" est meilleur que "FreeAndNil". Toutes les autres situations sont moins précises. Pour réitérer: je * toujours * nil mes références d'objet lors de la destruction des instances d'objets, de sorte que je n'ai jamais à penser si j'ai besoin ou non. –

5

Je ai eu tendance à utiliser FreeAndNil assez souvent (pour une raison quelconque) mais pas plus. Ce qui m'a fait arrêter de faire cela n'est pas lié à la nécessité ou non de la variable nil par la suite. Cela est lié aux changements de code, en particulier les changements de type de variables.

J'ai été mordu plusieurs fois après avoir changé le type d'une variable de TSomething à une interface de type ISomething. FreeAndNil ne se plaint pas et continue heureusement à faire son travail sur la variable d'interface. Cela a parfois conduit à des accidents mystérieux qui n'ont pas pu être immédiatement suivis à l'endroit où cela s'est produit et ont pris du temps à trouver. J'ai donc changé pour appeler Free. Et quand je le juge nécessaire, je définis la variable à nil ensuite explicitement.

3

i pourchassent une question stackoverflow parler de FreeAndNil et FreeAndInvalidate mentionner que le Microsoft Security Development Lifecycle maintenant recommends something similar to FreeAndInvalidate:

À la lumière de la recommandation SDL ci-dessus - et un certain nombre de bugs réels liés à la réutilisation des Références périmées aux objets C++ supprimés ...

Le choix évident de la valeur de nettoyage est NULL. Cependant, il y a des inconvénients: nous savons qu'un grand nombre de plantages d'applications sont dus à des déréférences de pointeurs NULL. Choisir NULL comme valeur de désinfection signifierait que les nouveaux plantages introduits par cette fonctionnalité seront moins susceptibles de se révéler à un développeur comme nécessitant une bonne solution - ie une gestion correcte des durées de vie des objets C++ - plutôt qu'une simple vérification NULL qui supprime le symptôme immédiat . Les vérifications de NULL sont une construction de code commune signifiant qu'une vérification existante pour NULL combinée à l'utilisation de NULL comme valeur de nettoyage pourrait cacher fortuitement un vrai problème de sécurité de la mémoire dont la cause première doit vraiment être adressée. Pour cette raison, nous avons choisi 0x8123 comme valeur de désinfection - du point de vue du système d'exploitation, il s'agit de la même page mémoire que l'adresse zéro (NULL), mais une violation d'accès à 0x8123 sera mieux visible pour le développeur. nécessitant une attention plus détaillée.

Donc, en gros:

procedure FreeAndInvalidate(var obj); 
var 
    temp : TObject; 
const 
    INVALID_ADDRESS = $8123; //address on same page as zero address (nil) 
begin 
    //Note: Code is public domain. No attribution required. 
    temp := TObject(obj); 
    Pointer(obj) := Pointer(INVALID_ADDRESS); 
    temp.Free; 
end;