58 votes

Pourquoi ne devrais-je pas utiliser "if Assigned ()" avant d'utiliser ou de libérer des éléments?

Cette question est la suite d'un commentaire de gens sur stackoverflow qui, je l'ai vu quelques fois maintenant. J'ai, avec le développeur qui m'a enseigné à Delphes, dans le but de garder les choses sûr, ont toujours mis un chèque if assigned() avant de libérer les objets, et avant de faire d'autres choses diverses. Cependant, je me suis dit que je devrais pas être en ajoutant cette case. Je voudrais savoir si il y a une différence dans la façon dont l'application compile/fonctionne si je fais cela, ou si elle n'affecte pas le résultat à toutes les...

if assigned(SomeObject) then SomeObject.Free;

Disons que j'ai un formulaire, et je suis de la création d'un objet bitmap dans le fond sur la forme de la création, et de la libérer quand je suis fait avec elle. Maintenant, je suppose que mon problème est que j'ai trop l'habitude de mettre ce contrôle sur une grande partie de mon code quand j'essaye d'accéder à des objets qui pourraient avoir été libre avais à un certain point. J'ai été de les utiliser même quand il n'est pas nécessaire. J'aime être approfondie...

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs;

type
  TForm1 = class(TForm)
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    FBitmap: TBitmap;
  public
    function LoadBitmap(const Filename: String): Bool;
    property Bitmap: TBitmap read FBitmap;
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
begin
  FBitmap:= TBitmap.Create;
  LoadBitmap('C:\Some Sample Bitmap.bmp');
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  if assigned(FBitmap) then begin //<-----
    //Do some routine to close file
    FBitmap.Free;
  end;
end;

function TForm1.LoadBitmap(const Filename: String): Bool;
var
  EM: String;
  function CheckFile: Bool;
  begin
    Result:= False;
    //Check validity of file, return True if valid bitmap, etc.
  end;
begin
  Result:= False;
  EM:= '';
  if assigned(FBitmap) then begin //<-----
    if FileExists(Filename) then begin
      if CheckFile then begin
        try
          FBitmap.LoadFromFile(Filename);
        except
          on e: exception do begin
            EM:= EM + 'Failure loading bitmap: ' + e.Message + #10;
          end;
        end;
      end else begin
        EM:= EM + 'Specified file is not a valid bitmap.' + #10;
      end;
    end else begin
      EM:= EM + 'Specified filename does not exist.' + #10;
    end;
  end else begin
    EM:= EM + 'Bitmap object is not assigned.' + #10;
  end;
  if EM <> '' then begin
    raise Exception.Create('Failed to load bitmap: ' + #10 + EM);
  end;
end;

end.

Maintenant, disons que je suis l'introduction d'une nouvelle liste personnalisée objet appelé TMyList de TMyListItem. Pour chaque élément de cette liste de cours que j'ai créer/gratuit chaque élément de l'objet. Il y a quelques différentes façons de créer un élément, ainsi que quelques différentes façons de détruire un élément (Ajout/Suppression d'être le plus commun). Je suis sûr que c'est une très bonne pratique de mettre cette protection ici...

procedure TMyList.Delete(const Index: Integer);
var
  I: TMyListItem;
begin
  if (Index >= 0) and (Index < FItems.Count) then begin
    I:= TMyListItem(FItems.Objects[Index]);
    if assigned(I) then begin //<-----
      if I <> nil then begin
        I.DoSomethingBeforeFreeing('Some Param');
        I.Free;
      end;
    end;
    FItems.Delete(Index);
  end else begin
    raise Exception.Create('My object index out of bounds ('+IntToStr(Index)+')');
  end;
end;

Dans de nombreux scénarios, au moins j'espère que l'objet est toujours créé avant j'ai essayer de le dégager. Mais vous ne savez jamais ce que les feuillets qui pourrait arriver dans le futur où un objet se dégage avais avant qu'il est censé. J'ai toujours utilisé cette case, mais maintenant je me dit que je ne devrais pas, et je ne comprends toujours pas pourquoi.


MODIFIER

Voici un exemple pour essayer de vous expliquer pourquoi j'ai l'habitude de le faire:

procedure TForm1.FormDestroy(Sender: TObject);
begin
  SomeCreatedObject.Free;
  if SomeCreatedObject = nil then
    ShowMessage('Object is nil')
  else
    ShowMessage('Object is not nil');
end;

Mon point est qu' if SomeCreatedObject <> nil n'est pas le même que if Assigned(SomeCreatedObject) car après la libération SomeCreatedObject, il n'évalue pas à l' nil. Donc, les deux contrôles nécessaires.

134voto

David Heffernan Points 292687

C'est une très vaste question avec de nombreux angles différents.

Le sens de l' Assigned fonction

Une grande partie du code dans votre question trahit une mauvaise compréhension de l' Assigned fonction. La documentation stipule ceci:

Les Tests pour un montant de néant (non affecté) pointeur de procédure ou de variable.

Utilisation Attribué pour déterminer si le pointeur ou d'une procédure référencé par P est nul. P doit être une variable de référence d'un pointeur ou de procédure type. Affecté(P) correspond au test P<> nil pour un pointeur variable, et @P <> nil pour une procédure variable.

Attribué retourne Faux si P est nul, True sinon.

Note: Attribué ne peut pas détecter un balançant pointeur--qui est, celui qui n'est pas nul, mais pas plus de points à des données valides. Par exemple, dans l'exemple de code Attribué, Attribué ne détecte pas que P n'est pas valide.

Les points clés à prendre à partir de cela sont les suivantes:

  1. Assigned est équivalent au test <> nil.
  2. Assigned ne peut pas détecter si le pointeur ou une référence d'objet est valide ou pas.

Ce que cela signifie dans le contexte de cette question est que

if obj<>nil

et

if Assigned(obj)

sont complètement interchangeables.

Test Assigned avant d'appeler Free

La mise en œuvre de l' TObject.Free est très spécial.

procedure TObject.Free;
begin
  if Self <> nil then
    Destroy;
end;

Cela vous permet d'appeler Free sur un objet de référence, c'est - nil et cela n'a aucun effet. Pour ce que cela vaut, je suis au courant d'aucun autre endroit dans le RTL/VCL où un tel truc est utilisé.

La raison pour laquelle vous souhaitez autoriser Free d'être appelé sur un nil référence de l'objet provient de la façon dont les constructeurs et destructeurs fonctionner en Delphi.

Lorsqu'une exception est levée dans un constructeur, le destructeur est appelé. Ceci est fait dans le but de libérer des ressources qui ont été allouées dans le cadre de l'constructeur qui a réussi. Si Free n'a pas été mis en œuvre comme il est alors destructeurs devra ressembler à ceci:

if obj1 <> nil then
  obj1.Free;
if obj2 <> nil then
  obj2.Free;
if obj3 <> nil then
  obj3.Free;
....

La prochaine pièce du puzzle est que Delphi constructeurs initialiser l'instance de la mémoire à zéro. Cela signifie que tout objet non affecté champs de référence sont nil.

Mettez tout cela ensemble, et le destructeur de code devient maintenant

obj1.Free;
obj2.Free;
obj3.Free;
....

Vous devez choisir la dernière option, car il est beaucoup plus lisible.

Il existe un scénario où vous avez besoin de tester si la référence est affecté dans un destructeur. Si vous avez besoin d'appeler une méthode sur l'objet avant de les détruire ensuite, il est évident qu'il doit se prémunir contre la possibilité de l' nil. Donc, ce code serait courir le risque d'une violation d'accès si elle est apparue dans un destructeur:

FSettings.Save;
FSettings.Free;

Au lieu de vous écrire

if Assigned(FSettings) then
begin
  FSettings.Save;
  FSettings.Free;
end;

Test Assigned à l'extérieur d'un destructeur

Vous aussi vous parlez d'écrire le code défensif à l'extérieur d'un destructeur. Par exemple:

constructor TMyObject.Create;
begin
  inherited;
  FSettings := TSettings.Create;
end;

destructor TMyObject.Destroy;
begin
  FSettings.Free;
  inherited;
end;

procedure TMyObject.Update;
begin
  if Assigned(FSettings) then
    FSettings.Update;
end;

Dans cette situation, il est encore une fois pas besoin de test Assigned en TMyObject.Update. La raison en est que vous simplement ne peut pas appeler TMyObject.Update , à moins que le constructeur de l' TMyObject réussi. Et si le constructeur de TMyObject réussi alors vous savez certainement que FSettings a été affecté. À nouveau vous rendre votre code beaucoup moins lisible et plus difficile à maintenir, par la mise en fausse appels d' Assigned.

Il y a un scénario dans lequel vous devez écrire if Assigned , et c'est là l'existence de l'objet en question est facultative. Par exemple

constructor TMyObject.Create(UseLogging: Boolean);
begin
  inherited Create;
  if UseLogging then
    FLogger := TLogger.Create;
end;

destructor TMyObject.Destroy;
begin
  FLogger.Free;
  inherited;
end;

procedure TMyObject.FlushLog;
begin
  if Assigned(FLogger) then
    FLogger.Flush;
end;

Dans ce scénario, la classe prend en charge deux modes de fonctionnement, avec et sans enregistrement. La décision est prise au moment de la construction et toutes les méthodes qui renvoient à l'enregistrement de l'objet doit vérifier son existence.

Ce n'est pas rare forme de code il est encore plus important que vous n'utilisez pas les parasites des appels d' Assigned pour les non-objets facultatifs. Quand vous voyez if Assigned(FLogger) dans le code qui devrait être une indication claire que la classe peut fonctionner normalement, FLogger pas dans l'existence. Si vous pulvérisez gratuite des appels d' Assigned autour de votre code puis vous perdre la capacité à dire à un coup d'œil si un objet doit toujours exister.

22voto

Joe White Points 32629

Free a certains de logique: il vérifie si Self est nil, et, dans l'affirmative, il retourne sans rien faire -- de sorte que vous pouvez appeler en toute sécurité X.Free même si X est nil. Ceci est important lorsque vous écrivez destructeurs -- David a plus de détails dans sa réponse.

Vous pouvez regarder le code source pour Free pour voir comment il fonctionne. Je n'ai pas la source Delphi à portée de main, mais c'est quelque chose comme ceci:

procedure TObject.Free;
begin
  if Self <> nil then
    Destroy;
end;

Ou, si vous préférez, vous pourriez considérer comme l'équivalent du code à l'aide d' Assigned:

procedure TObject.Free;
begin
  if Assigned(Self) then
    Destroy;
end;

Vous pouvez écrire vos propres méthodes qui permettent de vérifier if Self <> nil, tant qu'ils sont statiques (c'est à dire, pas virtual ou dynamic) des méthodes d'instance (merci à David Heffernan de documentation pour le lien). Mais dans la bibliothèque Delphi, Free est la seule méthode que je connaisse qui utilise cette astuce.

Si vous n'avez pas besoin de vérifier pour voir si la variable est - Assigned avant vous appelez Free; il le fait déjà pour vous. C'est la raison pour laquelle la recommandation est d'appeler Free plutôt que d'appeler Destroy directement: si vous avez appelé Détruire sur un nil de référence, vous obtenez une violation d'accès.

20voto

Martin Reiner Points 796

Pourquoi ne pas appeler

 if Assigned(SomeObject) then 
  SomeObject.Free;
 

Tout simplement parce que vous exécuteriez quelque chose comme ça

 if Assigned(SomeObject) then 
  if Assigned(SomeObject) then 
    SomeObject.Destroy;
 

Si vous appelez seulement SomeObject.Free; alors c'est juste

   if Assigned(SomeObject) then 
    SomeObject.Destroy;
 

Pour votre mise à jour, si vous avez peur de la référence d'instance d'objet, utilisez FreeAndNil. Il va détruire et déréférencer votre objet

 FreeAndNil(SomeObject);
 

C'est comme si vous appeliez

 SomeObject.Free;
SomeObject := nil;
 

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X