Внимание! Пост направлен на обоснование моей точки зрения :D Просьба все негативные проклятия изливать где-то ещё ;)
Примечание: если вы плохо или совсем не понимаете, что такое указатели и/или объекты - рекомендую сначала прочитать эту статью.
Рассмотрим типичный код создания и удаления объекта, как его обычно приводят:
... var SomeObj: TSomeClass; ... SomeObj := TSomeClass.Create; try ... finally SomeObj.Free; end; ...Однако, я хочу показать, что вызова
Free
следует избегать, где это возможно, заменяя его на вызов FreeAndNil
, вот так:... var SomeObj: TSomeClass; ... SomeObj := TSomeClass.Create; try ... finally FreeAndNil(SomeObj); end; ...Заметьте, что речь идёт именно о замене
Free
на FreeAndNil
везде. Не просто об использовании FreeAndNil
, когда вы хотите проверять ссылку на nil
, а именно - целиком и полностью везде. Т.е. не писать Free
вообще никогда. Да, включая сценарии с локальными переменными.Почему? Ну причина проста - нет никаких доводов так не делать (пожалуйста, дочитайте до конца). Зато есть доводы против использования
Free
в этих ситуациях.Обычно "против" выдают такие доводы:
1. Ну, например:
Free + nil
не полностью эквивалентен FreeAndNil
("использовать FreeAndNil
нужно аккуратно, потому что несмотря на название, там сначала происходит обнуление ссылки, а потом вызов Free
. Если в деструкторе или методах из него вызванных нам ссылка нужна - приплыли. Например, поле класса. Мы его занулили, потом вызвали деструктор, а внутренний класс может что-то потребовать у внешнего, в т.ч. через это поле").Это как раз плюс. Помогает отловить плохие ситуации. Потому что в описываемой ситуации идёт ссылка на объект через переменную, а не через
Self
. Заодно и избавимся от плохого стиля. Кроме того, зачем это внешнему классу что-то требовать у своего поля в момент удаления этого поля? По-моему, это логическая ошибка в явном виде (обращение к объекту в момент его удаления, т.е. к частично-инициализированному объекту). И вот использование
FreeAndNil
как раз позволит отловить такие ситуации. Конечно, такая ситуация может быть заранее предусмотрена, но это совершенно некрасиво (шаг в сторону, любое изменение кода "не в тему" и ваш код перестанет работать). В любом случае, такая ситуация не плавающая и обнаружится сразу же. После чего вы спокойно вернёте Free
на место.2. Другое возможное возражение: привести пример оправданного использования
FreeAndNil
.Не очень понятно, какого рода пример тут можно привести. Ведь использование
FreeAndNil
вместо Free
- это же совершенно опциональное действие. Не считая запутанных примеров, один и тот же корректный код будет работать совершенно одинаково, что с Free
, что с FreeAndNil
.FreeAndNil
чем-то подобен ремням безопасности: если прогон прошёл в штатном режиме - они не пригодились. Но если ваш код где-то напутал в последовательности действий, то FreeAndNil
(как и ремни безопасности) защитят вас от последствий. Обнулив ссылку, FreeAndNil
поможет поймать левое обращение сразу же, на месте. Без него код мог продолжить своё выполнение и дать неверный результат без возбуждения ошибки. Это очень опасно.Замечу, что, тем не менее
FreeAndNil
- это НЕ панацея, т.к. обращение к объекту может идти по нескольким переменным.3.
FreeAndNil
тут излишен! (локальные переменные)Локальную переменную могут потом сделать глобальной или частично-глобальной.
FreeAndNil
защитит нас от double-free. Просто Free
- нет. Очень большое количество кода получается использованием copy-paste. Если стоит Free
, то, скопировав код в другое место (где у переменной другая область видимости или она повторно используется) мы можем получить проблемы - с FreeAndNil
таких проблем нет. Кроме того, если у вас большая процедура, то вы можете, не заметив, дважды использовать одну переменную (например - в цикле). Всегда используя FreeAndNil
вы делаете свой код безопасным (к модификациям).Ладно, это был слабый аргумент. А реальная причина - единообразие стиля. Удобно, когда везде написано
FreeAndNil
вместо помеси Free/FreeAndNil
. И сам не запутаешься, когда что ставить (вам даже не надо думать: "ааа, здесь надо FreeAndNil
или можно просто Free
?!!").Если в каких-то ситуациях
FreeAndNil
действительно излишен, то чего ж вы тогда не используете вызов Destroy
? Ведь Free
во многих ситуациях тоже излишен (я ни в коем разе не имел ввиду деструктор! Я сказал: во многих случаях. Случай удаления частично-инициализированного объекта в них, разумеется, не входит).Заметьте, что в 99% кода на Delphi деструктор объектов не вызывается вообще! Нас уже приучили использовать вызов процедуры
Free
. А ведь когда-то народ, просто привыкший везде писать Destroy
, тоже кричал: "зачем нам этот Free
? Не нужен он тут! Где надо, я сам всё поставлю!". Ну и что? Пишем же мы сейчас все Free
? (ладно, вообще-то этот пример - не факт, а моя фантазия, т.к. я уже начал подзабывать - как оно там на самом деле было, в те времена. Но могу легко себе это представить, и, вроде бы, это кажется правдоподобным :) ).Ну так вот я агитирую за то, чтобы сделать ещё шаг вперёд: использовать
FreeAndNil
вместо Free
. Ведь польза от перехода Free
-> FreeAndNil
гораздо больше, чем польза от уже случившегося перехода от Destroy
к Free
.В первом случае мы получаем авто-защиту от плавающих ошибок (как я уже сказал, это не панацея, но, тем не менее, - существенный бонус). Во втором случае мы получили всего лишь возможность не писать явно
if
. Почему? Потому что если бы мы явно вызывали Destroy
, вместо Free
, то мы запустили бы деструктор с Self = nil
, что немедленно привело бы к AV при первом же обращении к полю объекта. Ошибка совершенно не плавающая и легко отлавливается. Т.е. это чисто экономия времени на ввод, без дополнительных бонусов. Согласитесь, что бонус от первого перехода ("защита от ошибок") намного более существенен, чем бонус от второго ("короче писать"). Тем более, что вы можете не терять бонус "короче писать", введя процедуру с именем F
, которая будет просто вызывать FreeAndNil
. Или вы можете сделать шаблон LiveTemplate, как это сделал я.4. Ещё один аргумент против: использование
Free
вместо Destroy
, настоятельно рекомендуется самим Borland/CodeGear/Embarcadero, чего не скажешь про FreeAndNil
.Да, точно. Как будто эти рекомендации кто-то менял со времён динозавров :)
Я уже привёл аргументацию, что переход
Free -> FreeAndNil
имеет бОльшую ценность, чем переход Destroy -> Free
. С учётом этого, аргумент отсутствия официального "добро" на FreeAndNil
выглядит бледно.5. Тем не менее, большинство людей, которые слышат о доводах к повсеместному использованию
FreeAndNil
вместо Free
, утверждают, что это перебор.Ну а почему?
Обычно ссылаются на силу привычки: "Free пишу на автомате, не задумываясь".
Кстати, второе возражение аналогичного плана - что
Obj.Free
короче, чем FreeAndNil(Obj)
; Это начинаются совсем уж мелкие придирки, потому что, даже если вы не имеете возможность использовать Code Templates, всегда можно ввести свою функцию F(Obj)
, которая будет просто вызывать FreeAndNil(Obj)
.Но вот аргумент привычки - это достаточно серьёзный контр-довод.
Что я могу сказать? Хм, ну а вот у меня уже привычка писать
FreeAndNil
:) Тоже на автомате. Я себе даже Code Template на F + Tab забиндил: автоматом вставить FreeAndNil(Obj)
и выделить Obj
, чтобы я сразу впечатал имя переменной. Очень удобно, всего две кнопки, и скобки ставить не надо.Этой статьёй я как раз хочу показать, что в данном случае имеются веские доводы к смене своих привычек. Конечно, ломать привычки - для этого нужен серьёзный повод. И вот тут мы подходим к...
6. Безопасность программирования.
Многие говорят, что опытный программист может ставить
FreeAndNil
только там, где это необходимо. Если грамотно использовать объекты (например, уничтожать объекты исключительно в деструкторе), то не будет нужды в FreeAndNil
.Но это не верно. Вы не можете этого знать.
Представим, что в деструкторе объекта (объект-контейнер) мы удаляем объектное поле с помощью
Free
. В деструкторе этого поля вызывается виртуальный метод, который ничего не делает в базовом классе, а в каком-то далёком предке вызывает последовательность действий, которая приводит к вызову виртуального-же метода объекта-контейнера. Который, в свою очередь, в каком-нибудь далёком предке (ошибочно) обращается к нашему удаляемому полю. При этом, обращение проходит на ура, но общее состояние становится безвозвратно испорченным. Упс.Почему я тут везде поставил виртуальные методы? Затем, чтобы нельзя было сказать: вот, смотрите, в моих классах нет нужды использовать
FreeAndNil
. Дело в том, что вы не можете этого знать! Вы-то создали чистую реализацию, а вот кто-нибудь другой создаст наследника, в котором неаккуратно вызовет в деструкторе какой-нибудь метод, приводящий (быть может далеко косвенно) к чтению уже удалённых или находящихся в процессе удаления объектов. FreeAndNil
защитит вас от такой ситуации, Free
- нет.Далее, если продолжить мысль с вставкой
FreeAndNil
только в необходимые места - так ведь речь как-раз идёт о том, что это не всегда бывает видно. Взяв в привычку повсеместно писать FreeAndNil
вы избавляете себя от этих проблем. И не надо ломать голову.Проблема даже в том, что если вы привыкли использовать
Free
, то во большинстве случаем вам даже не придёт в голову задуматься: а не нужен ли здесь FreeAndNil
? (хороший пример с деструктором объекта выше: вот могли ли вы предусмотреть эту ситуацию заранее?). И вот отсюда-то и лезут проблемы.Как я уже сказал:
FreeAndNil
- это ремни безопасности. Осталось только это осознать.Так вот, на мой взгляд, потенциальный бонус помощи в локализации плавающей ошибки (которая вообще может не проявиться, хотя бы в виде исключения) перевешивает необходимость смены привычки.
_________________________________________________________
Итак.
Так зачем же медлить? Почему бы, начиная с сегодняшнего дня, везде (где это возможно) использовать
FreeAndNil
вместо Free
? Если вы сделаете это своей привычкой - вы ничего не потеряете, а, наоборот, приобретёте немалые бонусы.Я не единожды сталкивался с трудно выловимыми багами в чужом коде со сложной иерархией классов. Потратив несколько часов на бесплодные попытки найти источник проблем "в лоб", просто тупо заменив все
Free
на FreeAndNil
, проблема находилась сразу же (но вот над решением проблемы приходилось ещу долго думать).Тем не менее, я подозреваю, что большинство людей, даже если они согласятся с приведённой выше аргументацией, не будут ломать свои привычки только по той причине, что "кто-то там что-то сказал". Они не встречались с такой ситуацией - значит, её нет (пока гром не грянет...). Как я уже сказал,
FreeAndNil
- это ремни безопасности. Пока какой-нибудь косяк не ударит вас сильно по носу, вы, скорее всего, не будете его использовать. Но уж будьте уверены, что когда он ударит - это будет весьма болезненно. Вы можете потратить на отладку кучу времени.Примечание: всё вышесказанное является только моим мнением. Я просто высказал точку зрения и привёл её аргументацию. Следовать этой рекомендации или нет - целиком ваше решение и я приму его, какое бы оно ни было. Если вы признали, что подобная точка зрения имеет право на жизнь - моя задача выполнена.
P.S. Насчёт "меньше печатать": создайте файл
FreeAndNil.xml
с таким содержанием:
<?xml version="1.0" encoding="utf-8" ?> <codetemplate xmlns="http://schemas.borland.com/Delphi/2005/codetemplates" version="1.0.0"> <template name="f" invoke="auto"> <point name="var"> <text> Variable </text> <hint> object to release </hint> </point> <description> release object and clear reference </description> <author> GunSmoker </author> <code language="Delphi" context="typedecl" delimiter="|"><![CDATA[FreeAndNil(|var|);]]> </code> </template> </codetemplate>И поместите его в папку
C:\Users\UserName\Documents\RAD Studio\code_templates\Delphi\
. Теперь для освобождения объекта достаточно набрать "f + пробел + имя-переменной".
Очень часто использую FreeAndNil при вызове вторых и третьих форм у главного окна. Перед вызовом всегда сначала проверяю на доступность окна проверкой вида "if fmSomeForm=Nil", и если условие выполняется, то второе окно создаю. В противном случае второе окно просто пытаюсь показать и выдвинуть на передний план.
ОтветитьУдалитьFreeAndNil даёт мне гарантию: если я вызвал FreeAndNil(fmSomeForm), то моя переменная fmSomeForm точно указывает на Nil, а не на какой-то мусор.
А я по привычке всегда ставлю Obj.Freу. Иногда потом думаю и добавляю Obj := nil. И только, редко-редко, по настроению пишу FreeAndNil.
ОтветитьУдалитьПричём, по закону Мерфи, я его начинаю писать обычно именно в юнитах, которые ещё не имеют Uses Sysutils :D Вот это, кстати меня обычно настраивает против FreeAndNil.
Ссылка в тему: на Sql.Ru обсуждают free и freeandnil.
ОтветитьУдалитьНе, там мы не гуляем ;)
ОтветитьУдалитьНо, except end - да, сильно :D
буквально на этой неделе словил багу из-за free. опыта мало , не знал , что он не обнуляет переменную. в итоге везде наставил obj:=nil
ОтветитьУдалитьсчас буду ставить freeandnil/
А вот ещё два примера в тему (и два разных способа поиска причин):
ОтветитьУдалитьПример 1Пример 2Правда, оба примера как раз на тему, когда просто FreeAndNil недостаточно - на объект ссылаются по нескольким ссылкам (как я и сказал в посте: FreeAndNil - не панацея).
:(
ОтветитьУдалитьЧто-то кривовато оформление вставилось.
Не хватает на blogger нормальной формочки отправки коммента, ох, как не хватает...
Я тоже за повсеместное FreeAndNil(Obj)...
ОтветитьУдалитьНо у меня один вопрос... Как быть с неменее типичной конструкцией?
with TSomeClass.Create do
try
...
finally
Free;
end;
Выходит все-таки без Free не обойтись?!
Разумеется, могут быть ситуации, когда обнуление ссылки невозможно. В примере с with это даже лишено смысла, т.к. ссылки у нас на руках нет и, следовательно, мы не можем по ней обратиться. Поэтому ничего страшного в этом нет.
ОтветитьУдалитьДругое дело, что сам with - страшен. Вам не следует использовать его.
> Не хватает на blogger нормальной формочки отправки коммента, ох, как не хватает...
ОтветитьУдалитьХехе, зато html разметка работает. =)
> Другое дело, что сам with - страшен. Вам не следует использовать его.
Не согласен. С ним намного удобней писать и редактировать код. А без него удобней отлаживать и разбираться в коде.
GunSmoker, я полностью отказался от использования with где бы то ни было кроме приведенного варианта (т.к. и впрямь код с ним мне читать хуже)... но поясни, пожалуйста, почему он страшен? ведь удобно же в данном случае (я использую такой вариант для диалоговых окон; хотя опять же нашел уже и минус - при вложенных with-конструкциях непонятки со свойствами и методами присутствующими в обоих классах, для которых создаются объекты)...
ОтветитьУдалитьСкажите, вот если вы в класс добавляете метод, которого нет ни у него, ни у его предков, вы ведь не ожидаете ничего плохого, верно?
ОтветитьУдалитьА проблема тут в том, что with смешивает в одну кучу методы текущего класса и класса в with.
Написали вы:
with TSomeClass.Create do
try
...
Show;
...
finally
Free;
end;
Подразумевая, Show - это метод формы. А автор класса TSomeClass потом решил, что было бы неплохо добавить возможность скрытия своих объектов, без удаления. И ввёл Show, Hide и Visible.
Вполне, казалось бы, безобидные изменения, верно? Да вот только теперь ваш код не работает, и попробуйте найти ошибку!
GunSmoker, я это и имел ввиду... только немного другой случай... в твоем случае я могу хотя бы указать Form.Show и непоняток не будет... а вот в случае вложенных with-create даже это не прокатит при наличии 2-х свойств/методов с одинаковыми названиями, т.к. я не имею имени переменной созданного объекта, чтобы указать чье свойство/метод использовать...
ОтветитьУдалитьGunSmoker:
ОтветитьУдалить> А автор класса TSomeClass потом решил, что было бы неплохо добавить возможность скрытия своих объектов, без удаления. И ввёл Show, Hide и Visible.
Не представлял такой ситуации. Буду реже использовать with и в случае обращения к методам предка - писать self. XD
Хотя в блоках with стараюсь работать только с за-with-ленным объектом.
Kbl4AH, вложенные with - это страшно.
Блин, а я всегда использовал FreeAndNil, причём не понимая почему именно эту процедуру, а не метод Free. Буду продолжать в том же духе.
ОтветитьУдалитьКстати, в комментах к посту в блоге EL дали такой контр-аргумент в виде интересной аналогии: ремни безопасности (равно как и FreeAndNil) расслабляют. Например, водитель может вести более беззаботно (зная, что он защищён), а программист - по невнимательности ввести double-free (т.к. об-nil-ение ссылки позволяет выполнять это безопасно).
ОтветитьУдалитьНасколько это ошибка проектирования или плохой стиль (и вредно ли это вообще) - это уже другой вопрос. Факт в том, что в случае double-free без FreeAndNil могло быть как лучше (AV при повторном освобождении) так и во много раз хуже (код деструктора выполнится дважды, т.к. менеджер памяти ещё не отпустил память).
Если вас действительно волнуют эти вопросы, заведите свой аналог FreeAndNil, в котором проверяйте аргумент на nil (да хотя бы Assert(Assigned(Pointer(Obj)))).
Кстати, тут один товарищ мне недавно втирал, что, мол, я пытаюсь изобрести панацею от всего, вместо того, чтобы выпрямить руки :D
ОтветитьУдалитьЧитайте, пожалуйста, внимательнее: я НЕ говорю, что FreeAndNil - это панацея от всех бед. Я говорю, что смысла НЕ использовать его нет. Точка. Всё остальное - ваши фантазии ;)
> Разумеется, могут быть ситуации, когда обнуление ссылки невозможно. В примере с with это даже лишено смысла, т.к. ссылки у нас на руках нет и, следовательно, мы не можем по ней обратиться.
ОтветитьУдалитьПросто для справки, при использовании with, если очень захочется, то ссылку получить можно:
type
TSimpleMethod = procedure of object;
function GetWithSelf(const pr:TSimpleMethod):TObject;
begin
Result := TMethod(pr).Data;
end;
Как видите, функция принимает указатель на метод, а возвращает обьект, являющийся владельцем этого метода. Но каким же методом мы воспользуемся? Например, метод Free, ведь его история восходит еще к самому TObject.
procedure ShowText(sl: TStringList);
begin
ShowMessage(sl.text);
end;
...
with TStringList.Create do
try
CommaText := '1,2,3,4,5,6,7,8,9,0';
ShowText(TStringList(GetWithSelf(Free)));
finally
Free;
end;
©DRKB
Да, спасибо, я в курсе. Ещё можно class helper-ы использовать. Но речь-то шла про конкретный пример. Если вы получаете ссылку на объект - with становится уж совсем не причём.
ОтветитьУдалить> Я говорю, что смысла НЕ использовать его нет.
ОтветитьУдалитьTMyObj1=Class
Private
FSubObj:TMyObj2;
....
End;
Destructor TMyObj1.Destroy;
Begin
// Вот здесь есть смысл его НЕ использовать
FSubObj.Free;
End;
ку?
Ну и в чём же смысл написания FSubObj.Free?
ОтветитьУдалитьЯ разбирал этот пример в п.6.
Здесь скорее нет смысла написания FreeAndNil. Про п.6 каюсь, как то мимо ушей его пропустил. Только он не затрагивает указанной мной ситуации. Во-первых поле приватное, соответственно никто из наследников не имеет к нему доступа, а во-вторых это просто агрегированный объект (создаваемый в конструкторе), соответсвенно и удаляться он должен в деструкторе. И трата времени на вызов и присвоение (в случае FreeAndNil) в некоторых случаях очень даже заметна. Особенно когда удаляются толпы мелких объектов на слабых машинах.
ОтветитьУдалитьДоступ к приватному полю может происходить из protected-метода.
ОтветитьУдалитьЯ лишь повторю ещё раз утверждение, которое почти все тоже пропускают мимо: "FreeAndNil - это ремни безопасности". Понятно, что вы можете отследить все случаи использования в законченной иерархии. Ремни безопасности страхуют вас от ошибок. Не более и не менее. Когда вы удаляете объект, но не очищаете ссылку, вы врёте машине. А она этого не любит. И рано или поздно это ударяет вас по лбу. Это вам надо?
Я действительно не понимаю, почему это простое утверждение так тяжело осознать.
> И трата времени на вызов и присвоение (в случае FreeAndNil) в некоторых случаях очень даже заметна.
Есть мнение, что вы видите что-то другое. Разница для 10 миллионов TComponent составляет 30 мс.
> Доступ к приватному полю может происходить из protected-метода
ОтветитьУдалитьПроисходить может, но не вижу причин вызывать protected-метод из деструктора.
В общем идея ясна. Холивар разводить нет смысла. Для новичков совет более чем полезен. На сим прошу пардону ) и не оспариваю.
> Есть мнение, что вы видите что-то другое.
) Я прекрасно вижу то, что я вижу на PIII-800 )
нафих мне лишние телодвижения демона не сдались )
> Я прекрасно вижу то, что я вижу на PIII-800
ОтветитьУдалить...чего-ж вы тогда Destroy не используете?.. ;)
ребята, это все чудесно, но "имеет очарование свеже изобретенного велосипеда" ;)
ОтветитьУдалитьмои возражения начинаются здесь -
"Локальную переменную могут потом сделать глобальной или частично-глобальной" - если у вас есть глобальные переменные или экземпляры класса, то на эту тему есть только 1 правильный ответ - шаблон Singleton. И вот в его реализации FreeAndNil - обоснованное решение... не будет вызывать подозрений дополненный FreeAndNil "Пример на Delphi" отсюда http://ru.wikipedia.org/wiki/%D0%9E%D0%B4%D0%B8%D0%BD%D0%BE%D1%87%D0%BA%D0%B0_%28%D1%88%D0%B0%D0%B1%D0%BB%D0%BE%D0%BD_%D0%BF%D1%80%D0%BE%D0%B5%D0%BA%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F%29
P.S. после освоения Singleton вы будете смотреть на глобальные переменные без использования оного как на безобразие, вызывающее упомянутые проблемы ;)
FreeAndNil - это не панацея от всех бед и не ремни безопасности. FreeAndNil - это средство, которое имеет свою область применения, свои плюсы и минусы. Лепить лозунги про "ремни безопасности", как упоминалось выше, не выбор хорошего программиста. Наличие самой возможности двойного вызова деструктора - плохой стиль программирования и дурной тон, чреватое утечками памяти и трудноотлавливаемыми багами.
ОтветитьУдалитьПример можно? :)
ОтветитьУдалитьПосле прочтения сей статьи решил взять себя в руки и переучиваться писать FreeAndNil, но сегодня напоролся на один интересный момент.
ОтветитьУдалитьСуть такова. Для хранения списка использовал TList. Код работал, перешел к оптимизации, и понял что лучше TList заменить на динамический массив (удобнее и быстрее). Стал заменить, заменил, скомпилировал, запустил, и вуаля. Программа вылетает с Access Violation-ами в очень интересных местах.
Как оказалось я в одном единственном месте забыл заменить FreeAndNil(MyList) на SetLength(MyList, 0). Компилятор совершенно здраво скомпилировал и запустил такую конструкцию ниразу не поперхнувшись и не известив ниодним варнингом.
Посему вердикт, что FreeAndNil должен быть таким:
procedure FreeAndNil(var Obj: TObject);
var
Temp: TObject;
begin
Temp := Obj;
Obj := nil;
Temp.Free;
end;
Уже в 5й версии делфи реализован код:
ОтветитьУдалитьprocedure FreeAndNil(var Obj);
var P: TObject;
begin
P := TObject(Obj);
TObject(Obj) := nil;
P.Free;
end;
в рамках подготовки перевода проекта Free --> FreeAndNil решил посмотреть, сколько раз во всех файлах проекта встречается строка ".Free" ........ 820 раз. Задумался, сколько времени понадобится на перевод, плюс еще бы багов не наплодить .....
ОтветитьУдалитьПереводить уже готовый и написанный проект с Free на FreeAndNil "просто так" - наверное, большого смысла в этом нет. Особенно, если вопрос стоит так: работает - и ладно, завтра сдаём.
ОтветитьУдалитьЭто имеет смысл делать, если вы собираетесь отлаживать какой-то глюк.
С другой стороны, если время позволяет - почему бы и нет. Практика показывает, что во время этого процесса могут найтись баги, которые не были замечены ранее ;) Например.
Просьба к автору статьи прокомментировать статью
ОтветитьУдалитьhttp://www.delphilab.ru/content/view/79/1/
по поводу не всегда правильной работы FreeAndNil.
(если надо я могу кинуть статью сюда в комментарий - она не большая)
Прочёлзаметку.
ОтветитьУдалитьЗаменил все вызовы Free на FreeAndNil.
Исправил ужасную ошибку, которую не мог отловить больше месяца.
Спасибо автору!
FreeAndNil хуже читаемый и расходящийся с объектным стилем записи кода, чем вызов метода объекта.
ОтветитьУдалитьДернуть за уже удаленный объект потомки могут, если в их деструкторе inherited Destroy идет не в конце.
Если в программе где-то нужно явно ставить
Obj := nil
имеет смысл саму себе привести доводы, почему это вдруг понадобилось вне рамок синглетона и сделать реструктуризацию.
FreeAndNil нужен прежде всего тем разработчикам, которые используют глобальные переменные модуля, чаще всего речь идет о формах.
Остальным лучше переходить работать в системы с автоматическим управлением памятью :)
Респект автору. Я давно уже стараюсь использовать исключительно конструкцию FreeAndNil вместо конструкции Free, за исключением тех моментов. когда это форма, создается она динамически и отображается посредством метода Execute, в котором в конце и разрушается, в то время как в вызывающем коде я не ввожу для этого отдельную переменную.
ОтветитьУдалитьТакже я НИКОГДА не использую конструкцию with. Хоть она возможно и позволяет писать код быстрее, однако это ее преимущество сходит "на нет" при попытке отдебажить данный код.
Программирую давно, опыта много, проблемы встречаются все реже и реже, теперь уже совсем редко.
ОтветитьУдалитьТем не менее, с FreeAndNil в последнее время было две проблемы, точнее одна и та же проблема, которая возникла дважды, последовательно в разных местах.
Приватное поле класса А - экземпляр класса В, в конструкторе класса А создается, в деструкторе разрушается (freeAndNil). Через некоторое, (продолжительное достаточно) время, ссылка B заменяется на интерфейсную (в рамках внедрения ioc)
И все, приплыли. Ошибка из серии "можно потратить много времени, пытаясь понять, что происходит".
Если бы там был вызов Free, проблема была бы обнаружена в компайл тайм, а не в ран тайм в виде испорченной памяти неизвестно где.
За это же время проблем с обращением к разрушенной и не обниленной переменной не было ни разу. Ни разу, понимаете? Это то, что реально имеется на практике.
Так что сабжевое утверждение - насчет "всегда нужно использовать", не соответствует истине. Не говоря уж о том, что обниливание - это совершенно ненужная в большинстве случаев операция, которая тем не менее занимает процессорное время. Пусть это копейки, но зачем? Зачем писать код, который не нужен, и который приводит к проблемам?
Я просто работаю в тех-поддержке EurekaLog. Этим Free я сыт по горло.
ОтветитьУдалитьЭто - инструкция для новичков.
Александр, вот пример противоположной точки зрения
ОтветитьУдалитьНика Ходжеса.
Порадовала фраза "Setting a pointer to nil doesn’t get you anything. The memory isn’t going to be more free or freed faster as a result of calling FreeAndNil. Since it’s always a good practice to use exactly the right tool and nothing more, there’s no need to make the extra call. Consider this – there’s no SetToZero call for integers, and if there were, why would you use it". ))
Я видел. Ник несколько постов писал, а всю серию подытожил примерно так: "вам не нужно использовать FreeAndNil, потому что вам нужно использовать интерфейсы". Кто бы спорил :)
ОтветитьУдалитьСам в delphi работаю давно. Сначала использовал FreeAndNil, потом перешел на Free, и сейчас много использую именно Free. Но в последнее время (года 3-5) стали лезть не совсем понятные ошибки: программа работает нормально, а стоит ее завершить, сразу лезет Exception, обращение к несуществующему адресу. Сильно подозреваю, что если не обнулить ссылку, сборщик мусора при завершении программы пытается повторно освободить память. Интересно, что ошибки не лезут в мелких программах, но стоит программе разрастись, начинают лезть. Поэтому как минимум надо сначала Free, потом еще и := nil. В связи с вышеизложенным в последнее время стал возвращаться к использованию FreeAndNil, ошибки вылазить перестали.
ОтветитьУдалитьЗа все 18-ть лет использования Delphi я пришёл ровно к тому, что написано в этой статье.
ОтветитьУдалитьОГРОМНОЕ спасибо автору.
Я себе даже Code Template на F + Tab забиндил: автоматом вставить FreeAndNil(Obj) и выделить Obj, чтобы я сразу впечатал имя переменной. Очень удобно, всего две кнопки, и скобки ставить не надо.
Я догадывался, что шаблоны где-то есть, но как-то опасался с ними связываться. А Ваши слова побудили меня с этим разобраться. Действительно удобно. Себе сделал так же и буду советовать другим.
Спасибо!
Free vs FreeAndNil - это тема для холивара))
ОтветитьУдалитьFreeAndNil конечно полезен, но лепить его везде - это, имхо, перебор. Просто лишние действия. Напр. для лок. переменных.
Огромное спасибо за статью. Избавила от большой головной боли с программой.
ОтветитьУдалитьА ещё грядущий учёт ссылок новых делфей однозначно приводит к FreeAndNil.
ОтветитьУдалитьзы. Меня как то приучили на студии нилить ссылки. И я быстро понял, что это хорошо.
http://programmingmindstream.blogspot.ru/2017/01/1334-free.html
ОтветитьУдалитьА что скажете про это?: https://habrahabr.ru/post/177431/
ОтветитьУдалитьА что вы хотите услышать? Если у вас есть такая проблема - действуйте.
Удалитьну...
Удалить>> речь идёт именно о замене Free на FreeAndNil везде
Посоветовали бы вы так же "сделать везде" то что предлагается в данной статье?
Как автор того поста скажу, что сейчас я бы сделал так
ОтветитьУдалитьunit FreeAndNilHelper;
interface
type
TFreeAndNilReservedType = TObject;
TObjectHelper = class helper for TObject
public
class procedure FreeAndNil(var Obj: T);
end;
procedure FreeAndNil(var Obj: TFreeAndNilReservedType);
implementation
class procedure TObjectHelper.FreeAndNil(var Obj: T);
var
Tmp: T;
begin
Tmp := Obj;
Obj := nil;
Tmp.Free;
end;
procedure FreeAndNil(var Obj: TFreeAndNilReservedType);
begin
//raise
end;
end.
И модуль подключить последним где нужно. Это лучше решает задачу проверки вызова FreeAndNil
парсер угловые скобки съел. Должно быть FreeAndNil[T: class] и внизу соответственно FreeAndNil[T]. Вместо квадратных скобок соответствующие угловые.
Удалить