Почему я не должен использовать «if Assigned ()» перед доступом к объектам? - PullRequest
56 голосов
/ 18 декабря 2011

Этот вопрос является продолжением отдельного комментария от людей о 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.Конечно, для каждого элемента в этом списке я должен создать / освободить каждый объект элемента.Существует несколько различных способов создания элемента, а также несколько различных способов уничтожения элемента (наиболее распространенными являются Add / Delete).Я уверен, что это очень хорошая практика, чтобы поместить эту защиту здесь ...

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;

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


РЕДАКТИРОВАТЬ

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

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 ]

123 голосов
/ 18 декабря 2011

Это очень широкий вопрос с разными углами.

Значение функции Assigned

Большая часть кода в вашем вопросе выдает неверныйпонимание функции Assigned.Документация гласит:

Проверяет ноль (неназначенный) указатель или процедурную переменную.

Использование Назначено , чтобы определить, является ли указатель или процедура, на которую ссылается P, nil .P должен быть ссылкой на переменную указателя или процедурного типа.

Назначено (P) соответствует тесту P <> nil для переменной указателя и @ P <> nil для процедурной переменной.

Назначено возвращает False , если P равно nil , True в противном случае.

Совет : при тестировании событий объекта и процедур для назначения нельзя выполнить проверку на nil и использовать Assigned правильный путь.

....

Примечание : Назначенный не может обнаружить висячий указатель, то есть тот, который не является nil , но это больше не указывает на действительные данные.

Значение Assigned отличается для указателя и процедурных переменных.В оставшейся части этого ответа мы рассмотрим только переменные-указатели, так как это контекст вопроса.Обратите внимание, что ссылка на объект реализована как переменная-указатель.

Ключевые моменты, которые следует извлечь из документации, для переменных-указателей:

  1. Assigned эквивалентно тестированию <> nil.
  2. Assigned не может определить, является ли указатель или ссылка на объект действительным или нет.

Что это означает в контексте этого вопроса, что

if obj<>nil

и

if Assigned(obj)

полностью взаимозаменяемы.

Тестирование Assigned перед вызовом Free

Реализация TObject.Free очень особенный.

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

Это позволяет вам вызывать Free для ссылки на объект nil, и это не имеет никакого эффекта.Что бы это ни стоило, я не знаю ни одного другого места в RTL / VCL, где бы использовался такой трюк.

Причина, по которой вы хотели бы разрешить вызов Free на nilссылка на объект проистекает из того, как конструкторы и деструкторы работают в Delphi.

Когда в конструкторе возникает исключение, вызывается деструктор.Это сделано для того, чтобы освободить любые ресурсы, которые были выделены в той части конструктора, которая преуспела.Если Free не был реализован как есть, то деструкторы должны были бы выглядеть следующим образом:

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

Следующий фрагмент головоломки состоит в том, что Delphi-конструкторы инициализируют память экземпляра в ноль ,Это означает, что любые неназначенные ссылочные поля объекта nil.

Соберите все это вместе, и код деструктора теперь станет

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

Вам следует выбрать последний вариант, потому что он намного более читабелен.

В одном сценарии вынужно проверить, назначена ли ссылка в деструкторе.Если вам нужно вызвать какой-либо метод объекта перед его уничтожением, тогда вы должны быть уверены, что он не может быть nil.Таким образом, этот код рискует получить AV, если он появится в деструкторе:

FSettings.Save;
FSettings.Free;

Вместо этого вы пишете

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

Тестирование Assigned вне деструктора

Вы также говорите о написании защитного кода вне деструктора.Например:

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;

В этой ситуации снова нет необходимости проверять Assigned в TMyObject.Update.Причина в том, что вы просто не можете вызвать TMyObject.Update, если конструктор TMyObject не был выполнен успешно.И если конструктор TMyObject завершился успешно, вы наверняка знаете, что FSettings был назначен.Итак, снова вы делаете свой код гораздо менее читабельным и трудным для поддержки, вводя ложные вызовы 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 вокруг вашего кода, то вы теряете возможность сразу определить, должен ли объект существовать всегда.

21 голосов
/ 18 декабря 2011

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) методы экземпляра (спасибо Дэвиду Хеффернану за ссылку на документацию).Но в библиотеке Delphi Free - единственный известный мне метод, использующий этот трюк.

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

16 голосов
/ 18 декабря 2011

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

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;
0 голосов
/ 16 марта 2016

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

if assigned(object.owner) then object.free 

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

if assigned(FBitmap.owner) then FBitmap.free
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...