Почему я не должен использовать "if Assigned()" перед доступом к объектам?


этот вопрос является продолжением конкретного комментария от людей на stackoverflow, который я видел несколько раз в настоящее время. Я вместе с разработчиком, который научил меня Delphi, чтобы держать вещи в безопасности, всегда ставил чек if assigned() перед освобождением объектов, и прежде чем делать другие различные вещи. Однако теперь мне говорят, что я должен не добавить этот чек. Я хотел бы знать, есть ли какая-либо разница в том, как приложение компилируется/запускается, если я это делаю, или если это не повлияет на результат...

if assigned(SomeObject) then SomeObject.Free;

допустим, у меня есть форма, и я создаю растровый объект в фоновом режиме при создании формы и освобождаю его, когда я закончу с ним. Теперь я думаю, что моя проблема в том, что я слишком привык ставить эту проверку на много моего кода, когда я пытаюсь получить доступ к объектам, которые потенциально могли бы быть свободными в какой-то момент. Я использую его даже когда в этом нет необходимости. Мне нравится быть доскональным...

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.

теперь давайте скажем Я представляю новый пользовательский объект списка под названием TMyList на TMyListItem. Для каждого элемента в этом списке, конечно, я должен создать / освободить каждый объект элемента. Существует несколько различных способов создания элемента, а также несколько различных способов уничтожения элемента (добавление/удаление является наиболее распространенным). Я уверен, что это очень хорошая практика, чтобы поставить эту защиту здесь...

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;

во многих сценариях, по крайней мере, я надеюсь, что объект все еще создается, прежде чем я попытаюсь его освободить. Но ты никогда ... знайте, что может произойти в будущем, когда объект освободится раньше, чем это предполагается. Я всегда использовал этот чек, но теперь мне говорят, что я не должен, и я до сих пор не понимаю, почему.


EDIT

вот пример, чтобы попытаться объяснить вам, почему у меня есть привычка делать это:

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

Я хочу сказать, что if SomeCreatedObject <> nil - это не то же самое как if Assigned(SomeCreatedObject) потому что после освобождения SomeCreatedObject, он не оценивает в nil. Так обе проверки должны быть обязательно.

4 52

4 ответа:

это очень широкий вопрос с разных точек зрения.

смысл Assigned функции

большая часть кода в вашем вопросе выдает неправильное понимание . Таким образом, вы снова делаете свой код гораздо менее читаемым и более сложным для обслуживания, помещая ложные вызовы в Assigned.

есть сценарий, где вам нужно написать if Assigned и именно там существование рассматриваемого объекта является необязательным. Например

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;

в этом сценарии класс поддерживает два режима работы, с журналированием и без него. Решение принимается во время строительства и любых методы, которые ссылаются на объект ведения журнала, должны проверить его существование.

эта не редкость форма кода делает его еще более важным, что вы не используете ложные вызовы Assigned на обязательные предметы. Когда вы видите if Assigned(FLogger) в коде, который должен быть четким указанием на то, что класс может нормально работать с FLogger не существует. Если вы распыляете безвозмездные звонки на Assigned вокруг вашего кода, то вы теряете возможность сразу сказать, является ли объект или нет должен существовать всегда.

Free имеет некоторую специальную логику: он проверяет, является ли Self - это nil, и если это так, он возвращается, ничего не делая - так что вы можете спокойно позвонить X.Free даже если X и nil. Это важно, когда вы пишете деструкторы-Дэвид имеет более подробную информацию в ответ.

вы можете посмотреть исходный код для Free чтобы увидеть, как это работает. У меня нет источника Delphi под рукой, но это что-то вроде этого:

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

или, если вы предпочитаете, вы могли бы думать об этом как об эквивалентном коде, используя Assigned:

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

вы можете написать свои собственные методы проверки if Self <> nil, пока они статический (т. е. не virtual или dynamic) методы экземпляра (спасибо Дэвиду Хеффернану за ссылку на документацию). Но в библиотеке Дельфы,Free это единственный способ я знаю, что использует этот трюк.

поэтому вам не нужно проверять, является ли переменная Assigned прежде чем позвонить Free; оно уже делает это для вас. Вот почему рекомендация состоит в том, чтобы позвонить Free вместо того, чтобы звонить Destroy напрямую: если вы назвали уничтожить на nil ссылка, вы получите нарушение прав доступа.

почему бы тебе не позвонить

if Assigned(SomeObject) then 
  SomeObject.Free;

просто потому, что вы бы выполнить что-то вроде этого

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

если вы позвоните просто SomeObject.Free; тогда это просто

  if Assigned(SomeObject) then 
    SomeObject.Destroy;

к вашему обновлению, если вы боитесь ссылки на экземпляр объекта, используйте FreeAndNil. Он уничтожит и разыменует ваш объект

FreeAndNil(SomeObject);

это похоже на то, как если бы вы позвонили

SomeObject.Free;
SomeObject := nil;

Я не совсем уверен, но кажется:

if assigned(object.owner) then object.free 

работает нормально. В этом примере это будет

if assigned(FBitmap.owner) then FBitmap.free