Почему я не должен использовать "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 ответа:
это очень широкий вопрос с разных точек зрения.
смысл
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;