15 апреля 2009 г.

Почему всегда нужно использовать FreeAndNil вместо Free

Это - собранные в кучу посты, которые потянулись за этим высказыванием.

Внимание! Пост направлен на обоснование моей точки зрения :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 + пробел + имя-переменной".

52 комментария :

  1. Очень часто использую FreeAndNil при вызове вторых и третьих форм у главного окна. Перед вызовом всегда сначала проверяю на доступность окна проверкой вида "if fmSomeForm=Nil", и если условие выполняется, то второе окно создаю. В противном случае второе окно просто пытаюсь показать и выдвинуть на передний план.

    FreeAndNil даёт мне гарантию: если я вызвал FreeAndNil(fmSomeForm), то моя переменная fmSomeForm точно указывает на Nil, а не на какой-то мусор.

    ОтветитьУдалить
  2. А я по привычке всегда ставлю Obj.Freу. Иногда потом думаю и добавляю Obj := nil. И только, редко-редко, по настроению пишу FreeAndNil.
    Причём, по закону Мерфи, я его начинаю писать обычно именно в юнитах, которые ещё не имеют Uses Sysutils :D Вот это, кстати меня обычно настраивает против FreeAndNil.

    ОтветитьУдалить
  3. Не, там мы не гуляем ;)
    Но, except end - да, сильно :D

    ОтветитьУдалить
  4. буквально на этой неделе словил багу из-за free. опыта мало , не знал , что он не обнуляет переменную. в итоге везде наставил obj:=nil
    счас буду ставить freeandnil/

    ОтветитьУдалить
  5. А вот ещё два примера в тему (и два разных способа поиска причин):
    Пример 1Пример 2Правда, оба примера как раз на тему, когда просто FreeAndNil недостаточно - на объект ссылаются по нескольким ссылкам (как я и сказал в посте: FreeAndNil - не панацея).

    ОтветитьУдалить
  6. :(
    Что-то кривовато оформление вставилось.
    Не хватает на blogger нормальной формочки отправки коммента, ох, как не хватает...

    ОтветитьУдалить
  7. Я тоже за повсеместное FreeAndNil(Obj)...
    Но у меня один вопрос... Как быть с неменее типичной конструкцией?
    with TSomeClass.Create do
    try
    ...
    finally
    Free;
    end;
    Выходит все-таки без Free не обойтись?!

    ОтветитьУдалить
  8. Разумеется, могут быть ситуации, когда обнуление ссылки невозможно. В примере с with это даже лишено смысла, т.к. ссылки у нас на руках нет и, следовательно, мы не можем по ней обратиться. Поэтому ничего страшного в этом нет.

    Другое дело, что сам with - страшен. Вам не следует использовать его.

    ОтветитьУдалить
  9. > Не хватает на blogger нормальной формочки отправки коммента, ох, как не хватает...
    Хехе, зато html разметка работает. =)

    > Другое дело, что сам with - страшен. Вам не следует использовать его.
    Не согласен. С ним намного удобней писать и редактировать код. А без него удобней отлаживать и разбираться в коде.

    ОтветитьУдалить
  10. GunSmoker, я полностью отказался от использования with где бы то ни было кроме приведенного варианта (т.к. и впрямь код с ним мне читать хуже)... но поясни, пожалуйста, почему он страшен? ведь удобно же в данном случае (я использую такой вариант для диалоговых окон; хотя опять же нашел уже и минус - при вложенных with-конструкциях непонятки со свойствами и методами присутствующими в обоих классах, для которых создаются объекты)...

    ОтветитьУдалить
  11. Скажите, вот если вы в класс добавляете метод, которого нет ни у него, ни у его предков, вы ведь не ожидаете ничего плохого, верно?

    А проблема тут в том, что with смешивает в одну кучу методы текущего класса и класса в with.

    Написали вы:

    with TSomeClass.Create do
    try
    ...
    Show;
    ...
    finally
    Free;
    end;

    Подразумевая, Show - это метод формы. А автор класса TSomeClass потом решил, что было бы неплохо добавить возможность скрытия своих объектов, без удаления. И ввёл Show, Hide и Visible.
    Вполне, казалось бы, безобидные изменения, верно? Да вот только теперь ваш код не работает, и попробуйте найти ошибку!

    ОтветитьУдалить
  12. GunSmoker, я это и имел ввиду... только немного другой случай... в твоем случае я могу хотя бы указать Form.Show и непоняток не будет... а вот в случае вложенных with-create даже это не прокатит при наличии 2-х свойств/методов с одинаковыми названиями, т.к. я не имею имени переменной созданного объекта, чтобы указать чье свойство/метод использовать...

    ОтветитьУдалить
  13. GunSmoker:
    > А автор класса TSomeClass потом решил, что было бы неплохо добавить возможность скрытия своих объектов, без удаления. И ввёл Show, Hide и Visible.
    Не представлял такой ситуации. Буду реже использовать with и в случае обращения к методам предка - писать self. XD
    Хотя в блоках with стараюсь работать только с за-with-ленным объектом.

    Kbl4AH, вложенные with - это страшно.

    ОтветитьУдалить
  14. Блин, а я всегда использовал FreeAndNil, причём не понимая почему именно эту процедуру, а не метод Free. Буду продолжать в том же духе.

    ОтветитьУдалить
  15. Кстати, в комментах к посту в блоге EL дали такой контр-аргумент в виде интересной аналогии: ремни безопасности (равно как и FreeAndNil) расслабляют. Например, водитель может вести более беззаботно (зная, что он защищён), а программист - по невнимательности ввести double-free (т.к. об-nil-ение ссылки позволяет выполнять это безопасно).

    Насколько это ошибка проектирования или плохой стиль (и вредно ли это вообще) - это уже другой вопрос. Факт в том, что в случае double-free без FreeAndNil могло быть как лучше (AV при повторном освобождении) так и во много раз хуже (код деструктора выполнится дважды, т.к. менеджер памяти ещё не отпустил память).

    Если вас действительно волнуют эти вопросы, заведите свой аналог FreeAndNil, в котором проверяйте аргумент на nil (да хотя бы Assert(Assigned(Pointer(Obj)))).

    ОтветитьУдалить
  16. Кстати, тут один товарищ мне недавно втирал, что, мол, я пытаюсь изобрести панацею от всего, вместо того, чтобы выпрямить руки :D

    Читайте, пожалуйста, внимательнее: я НЕ говорю, что FreeAndNil - это панацея от всех бед. Я говорю, что смысла НЕ использовать его нет. Точка. Всё остальное - ваши фантазии ;)

    ОтветитьУдалить
  17. > Разумеется, могут быть ситуации, когда обнуление ссылки невозможно. В примере с 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

    ОтветитьУдалить
  18. Да, спасибо, я в курсе. Ещё можно class helper-ы использовать. Но речь-то шла про конкретный пример. Если вы получаете ссылку на объект - with становится уж совсем не причём.

    ОтветитьУдалить
  19. > Я говорю, что смысла НЕ использовать его нет.

    TMyObj1=Class
    Private
    FSubObj:TMyObj2;
    ....
    End;

    Destructor TMyObj1.Destroy;
    Begin
    // Вот здесь есть смысл его НЕ использовать
    FSubObj.Free;
    End;

    ку?

    ОтветитьУдалить
  20. Ну и в чём же смысл написания FSubObj.Free?

    Я разбирал этот пример в п.6.

    ОтветитьУдалить
  21. Здесь скорее нет смысла написания FreeAndNil. Про п.6 каюсь, как то мимо ушей его пропустил. Только он не затрагивает указанной мной ситуации. Во-первых поле приватное, соответственно никто из наследников не имеет к нему доступа, а во-вторых это просто агрегированный объект (создаваемый в конструкторе), соответсвенно и удаляться он должен в деструкторе. И трата времени на вызов и присвоение (в случае FreeAndNil) в некоторых случаях очень даже заметна. Особенно когда удаляются толпы мелких объектов на слабых машинах.

    ОтветитьУдалить
  22. Доступ к приватному полю может происходить из protected-метода.
    Я лишь повторю ещё раз утверждение, которое почти все тоже пропускают мимо: "FreeAndNil - это ремни безопасности". Понятно, что вы можете отследить все случаи использования в законченной иерархии. Ремни безопасности страхуют вас от ошибок. Не более и не менее. Когда вы удаляете объект, но не очищаете ссылку, вы врёте машине. А она этого не любит. И рано или поздно это ударяет вас по лбу. Это вам надо?
    Я действительно не понимаю, почему это простое утверждение так тяжело осознать.

    > И трата времени на вызов и присвоение (в случае FreeAndNil) в некоторых случаях очень даже заметна.
    Есть мнение, что вы видите что-то другое. Разница для 10 миллионов TComponent составляет 30 мс.

    ОтветитьУдалить
  23. > Доступ к приватному полю может происходить из protected-метода

    Происходить может, но не вижу причин вызывать protected-метод из деструктора.

    В общем идея ясна. Холивар разводить нет смысла. Для новичков совет более чем полезен. На сим прошу пардону ) и не оспариваю.

    > Есть мнение, что вы видите что-то другое.
    ) Я прекрасно вижу то, что я вижу на PIII-800 )
    нафих мне лишние телодвижения демона не сдались )

    ОтветитьУдалить
  24. > Я прекрасно вижу то, что я вижу на PIII-800
    ...чего-ж вы тогда Destroy не используете?.. ;)

    ОтветитьУдалить
  25. ребята, это все чудесно, но "имеет очарование свеже изобретенного велосипеда" ;)
    мои возражения начинаются здесь -
    "Локальную переменную могут потом сделать глобальной или частично-глобальной" - если у вас есть глобальные переменные или экземпляры класса, то на эту тему есть только 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 вы будете смотреть на глобальные переменные без использования оного как на безобразие, вызывающее упомянутые проблемы ;)

    ОтветитьУдалить
  26. FreeAndNil - это не панацея от всех бед и не ремни безопасности. FreeAndNil - это средство, которое имеет свою область применения, свои плюсы и минусы. Лепить лозунги про "ремни безопасности", как упоминалось выше, не выбор хорошего программиста. Наличие самой возможности двойного вызова деструктора - плохой стиль программирования и дурной тон, чреватое утечками памяти и трудноотлавливаемыми багами.

    ОтветитьУдалить
  27. После прочтения сей статьи решил взять себя в руки и переучиваться писать 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;

    ОтветитьУдалить
  28. Уже в 5й версии делфи реализован код:
    procedure FreeAndNil(var Obj);
    var P: TObject;
    begin
    P := TObject(Obj);
    TObject(Obj) := nil;
    P.Free;
    end;

    ОтветитьУдалить
  29. в рамках подготовки перевода проекта Free --> FreeAndNil решил посмотреть, сколько раз во всех файлах проекта встречается строка ".Free" ........ 820 раз. Задумался, сколько времени понадобится на перевод, плюс еще бы багов не наплодить .....

    ОтветитьУдалить
  30. Переводить уже готовый и написанный проект с Free на FreeAndNil "просто так" - наверное, большого смысла в этом нет. Особенно, если вопрос стоит так: работает - и ладно, завтра сдаём.

    Это имеет смысл делать, если вы собираетесь отлаживать какой-то глюк.

    С другой стороны, если время позволяет - почему бы и нет. Практика показывает, что во время этого процесса могут найтись баги, которые не были замечены ранее ;) Например.

    ОтветитьУдалить
  31. Просьба к автору статьи прокомментировать статью
    http://www.delphilab.ru/content/view/79/1/
    по поводу не всегда правильной работы FreeAndNil.
    (если надо я могу кинуть статью сюда в комментарий - она не большая)

    ОтветитьУдалить
  32. Прочёлзаметку.
    Заменил все вызовы Free на FreeAndNil.
    Исправил ужасную ошибку, которую не мог отловить больше месяца.

    Спасибо автору!

    ОтветитьУдалить
  33. FreeAndNil хуже читаемый и расходящийся с объектным стилем записи кода, чем вызов метода объекта.

    Дернуть за уже удаленный объект потомки могут, если в их деструкторе inherited Destroy идет не в конце.

    Если в программе где-то нужно явно ставить
    Obj := nil
    имеет смысл саму себе привести доводы, почему это вдруг понадобилось вне рамок синглетона и сделать реструктуризацию.

    FreeAndNil нужен прежде всего тем разработчикам, которые используют глобальные переменные модуля, чаще всего речь идет о формах.

    Остальным лучше переходить работать в системы с автоматическим управлением памятью :)

    ОтветитьУдалить
  34. Респект автору. Я давно уже стараюсь использовать исключительно конструкцию FreeAndNil вместо конструкции Free, за исключением тех моментов. когда это форма, создается она динамически и отображается посредством метода Execute, в котором в конце и разрушается, в то время как в вызывающем коде я не ввожу для этого отдельную переменную.
    Также я НИКОГДА не использую конструкцию with. Хоть она возможно и позволяет писать код быстрее, однако это ее преимущество сходит "на нет" при попытке отдебажить данный код.

    ОтветитьУдалить
  35. Программирую давно, опыта много, проблемы встречаются все реже и реже, теперь уже совсем редко.
    Тем не менее, с FreeAndNil в последнее время было две проблемы, точнее одна и та же проблема, которая возникла дважды, последовательно в разных местах.
    Приватное поле класса А - экземпляр класса В, в конструкторе класса А создается, в деструкторе разрушается (freeAndNil). Через некоторое, (продолжительное достаточно) время, ссылка B заменяется на интерфейсную (в рамках внедрения ioc)
    И все, приплыли. Ошибка из серии "можно потратить много времени, пытаясь понять, что происходит".
    Если бы там был вызов Free, проблема была бы обнаружена в компайл тайм, а не в ран тайм в виде испорченной памяти неизвестно где.
    За это же время проблем с обращением к разрушенной и не обниленной переменной не было ни разу. Ни разу, понимаете? Это то, что реально имеется на практике.
    Так что сабжевое утверждение - насчет "всегда нужно использовать", не соответствует истине. Не говоря уж о том, что обниливание - это совершенно ненужная в большинстве случаев операция, которая тем не менее занимает процессорное время. Пусть это копейки, но зачем? Зачем писать код, который не нужен, и который приводит к проблемам?

    ОтветитьУдалить
  36. Я просто работаю в тех-поддержке EurekaLog. Этим Free я сыт по горло.

    Это - инструкция для новичков.

    ОтветитьУдалить
  37. Александр, вот пример противоположной точки зрения
    Ника Ходжеса.

    Порадовала фраза "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". ))

    ОтветитьУдалить
  38. Я видел. Ник несколько постов писал, а всю серию подытожил примерно так: "вам не нужно использовать FreeAndNil, потому что вам нужно использовать интерфейсы". Кто бы спорил :)

    ОтветитьУдалить
  39. Сам в delphi работаю давно. Сначала использовал FreeAndNil, потом перешел на Free, и сейчас много использую именно Free. Но в последнее время (года 3-5) стали лезть не совсем понятные ошибки: программа работает нормально, а стоит ее завершить, сразу лезет Exception, обращение к несуществующему адресу. Сильно подозреваю, что если не обнулить ссылку, сборщик мусора при завершении программы пытается повторно освободить память. Интересно, что ошибки не лезут в мелких программах, но стоит программе разрастись, начинают лезть. Поэтому как минимум надо сначала Free, потом еще и := nil. В связи с вышеизложенным в последнее время стал возвращаться к использованию FreeAndNil, ошибки вылазить перестали.

    ОтветитьУдалить
  40. За все 18-ть лет использования Delphi я пришёл ровно к тому, что написано в этой статье.

    ОГРОМНОЕ спасибо автору.

    ОтветитьУдалить

  41. Я себе даже Code Template на F + Tab забиндил: автоматом вставить FreeAndNil(Obj) и выделить Obj, чтобы я сразу впечатал имя переменной. Очень удобно, всего две кнопки, и скобки ставить не надо.

    Я догадывался, что шаблоны где-то есть, но как-то опасался с ними связываться. А Ваши слова побудили меня с этим разобраться. Действительно удобно. Себе сделал так же и буду советовать другим.
    Спасибо!

    ОтветитьУдалить
  42. Free vs FreeAndNil - это тема для холивара))
    FreeAndNil конечно полезен, но лепить его везде - это, имхо, перебор. Просто лишние действия. Напр. для лок. переменных.

    ОтветитьУдалить
  43. Огромное спасибо за статью. Избавила от большой головной боли с программой.

    ОтветитьУдалить
  44. А ещё грядущий учёт ссылок новых делфей однозначно приводит к FreeAndNil.
    зы. Меня как то приучили на студии нилить ссылки. И я быстро понял, что это хорошо.

    ОтветитьУдалить
  45. http://programmingmindstream.blogspot.ru/2017/01/1334-free.html

    ОтветитьУдалить
  46. А что скажете про это?: https://habrahabr.ru/post/177431/

    ОтветитьУдалить
    Ответы
    1. А что вы хотите услышать? Если у вас есть такая проблема - действуйте.

      Удалить
    2. ну...
      >> речь идёт именно о замене Free на FreeAndNil везде
      Посоветовали бы вы так же "сделать везде" то что предлагается в данной статье?

      Удалить
  47. Как автор того поста скажу, что сейчас я бы сделал так

    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

    ОтветитьУдалить
    Ответы
    1. парсер угловые скобки съел. Должно быть FreeAndNil[T: class] и внизу соответственно FreeAndNil[T]. Вместо квадратных скобок соответствующие угловые.

      Удалить

Можно использовать некоторые HTML-теги, например:

<b>Жирный</b>
<i>Курсив</i>
<a href="http://www.example.com/">Ссылка</a>

Вам необязательно регистрироваться для комментирования - для этого просто выберите из списка "Анонимный" (для анонимного комментария) или "Имя/URL" (для указания вашего имени и (опционально) ссылки на сайт). Все прочие варианты потребуют от вас входа в вашу учётку.

Пожалуйста, по возможности используйте "Имя/URL" вместо "Анонимный". URL можно просто не указывать.

Ваше сообщение может быть помечено как спам спам-фильтром - не волнуйтесь, оно появится после проверки администратором.

Примечание. Отправлять комментарии могут только участники этого блога.