Этот вопрос является продолжением конкретного комментария от людей о 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;
Во многих сценариях, по крайней мере, я надеюсь, что объект все еще будет создан, прежде чем я попытаюсь его освободить. Но вы никогда не знаете, какие промахи могут произойти в будущем, когда объект освобождается до того, как он это предположил. Я всегда пользовался этой проверкой, но теперь мне говорят, что я не должен, и я до сих пор не понимаю, почему.
ИЗМЕНИТЬ
Вот пример, чтобы попытаться объяснить вам, почему у меня есть привычка делать это:
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
. Поэтому должны быть необходимы обе проверки.
Это очень широкий вопрос с разных точек зрения.
Смысл Assigned
функции
Большая часть кода в вашем вопросе выдает неверное понимание Assigned
функции. Документация утверждает это:
Проверяет нулевой (неназначенный) указатель или процедурную переменную.
Используйте Назначено, чтобы определить, является ли указатель или процедура, на которую ссылается P, нулем. P должен быть ссылкой на переменную указателя или процедурного типа.
Назначенный (P) соответствует тесту P <> nil для переменной-указателя и @P <> nil для процедурной переменной.
Assigned возвращает False, если P равно нулю, в противном случае - True.
Совет: при тестировании событий объекта и процедур для назначения вы не можете проверить на ноль, и использование Assigned является правильным способом.
....
Примечание. Назначенный не может обнаружить висячий указатель, то есть тот, который не равен нулю, но больше не указывает на действительные данные.
Значение Assigned
отличается для указателя и процедурных переменных. В оставшейся части этого ответа мы рассмотрим только переменные-указатели, так как это контекст вопроса. Обратите внимание, что ссылка на объект реализована как переменная-указатель.
Ключевые моменты, которые следует взять из документации, для переменных-указателей:
Assigned
эквивалентно тестированию <> nil
.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
вокруг вашего кода, вы теряете возможность сразу определить, должен ли объект существовать всегда.
TMyObject.Destroy
вы вызываете FLogger.Free
не проверяя, назначен ли он. Это потому, что TMyObject.Create
всегда инициализирует его на ноль, когда UseLogging
имеет значение False? при объявлении локальной процедуры TObject в процедуре мы не можем просто вызвать object.Free без предварительной инициализации. или я не прав?
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
, если они static (т.е. не virtual
или dynamic
) методы экземпляра (спасибо Дэвиду Хеффернану за ссылку на документацию). Но в библиотеке Delphi Free
- единственный метод, который я знаю, который использует этот трюк.
Поэтому вам не нужно проверять, есть ли переменная Assigned
перед вызовом Free
; это уже делает это для вас. На самом деле, почему рекомендация заключается в вызове Free
, а не в вызове Destroy
напрямую: если вы вызвали Destroy на ссылку nil
, вы получили бы нарушение прав доступа.
assigned()
который работает немного по-другому
procedure of object
или function of object
), Assigned
- это то же самое, что проверка <> 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;
SomeObject.Free
уничтожает экземпляр, вызывая цепочку деструкторов и освобождая выделенную память. Это не меняет значение SomeObject
. Поскольку это указатель, то есть адрес памяти, он все равно будет нести этот же адрес после SomeObject.Free
Кроме того, AFAIK, освобожденная память не заполняется нулевыми байтами, как TObject.InitInstance
делает TObject.InitInstance
при создании. Поэтому, в идеале, используйте FreeAndNil( SomeObject )
иначе нет возможности отличить живые экземпляры от мертвых.
Я не совсем уверен в этом, но кажется:
if assigned(object.owner) then object.free
работает отлично. В этом примере это будет
if assigned(FBitmap.owner) then FBitmap.free
assigned(I)
отличается отI <> nil
? (Обратите внимание, что я вообще не использую Delphi: p ~)