Почему я должен использовать Free, а не FreeAndNil в деструкторе? - PullRequest
11 голосов
/ 18 августа 2010

Я прочитал Дело против FreeAndNil , но все еще не понимаю, почему я не могу использовать этот метод в деструкторе класса?Может кто-нибудь объяснить.

Обновление: Я думаю, что комментарий от Эрик Грандж был наиболее полезным для меня.По ссылке видно, что не совсем понятно, как с этим бороться, и это в основном дело вкуса.Также был полезен метод FreeAndInvalidate.

Ответы [ 4 ]

14 голосов
/ 18 августа 2010

Проблема в том, что многие кажется, использовать FreeAndNil как магию пуля, которая убьет эту таинственную крашеный дракон. При использовании FreeAndNil () в деструктор, кажется, решает аварию или другие проблемы с повреждением памяти, тогда вы должны копать глубже настоящая причина. Когда я вижу это, Первый вопрос, который я задаю, это почему доступ к полю экземпляра после этот экземпляр был уничтожен? Тот обычно указывает на проблему дизайна.

Оно утверждает, что оно скрывает реальную проблему, которая у вас есть. Это должно означать, что ваш код обращается к свойствам / полям / методам объекта, который уже уничтожен (вызывается деструктор). Поэтому вместо того, чтобы скрывать реальную проблему с FreeAndNil, вы должны действительно решить основную проблему.

Этот код не будет аварийно завершать работу, если вы будете использовать FreeAndNil PropertyA в деструкторе SomeObject. Но это скрывает реальную проблему, которую SomeObject использует после того, как это разрушено. Лучше решить эту проблему проектирования (доступ к разрушенным объектам), а не скрывать ее.

SomeObject.Free;  // Destructor called
if Assigned(SomeObject.PropertyA) then  // SomeObject is destroyed, but still used
  SomeObject.PropertyA.Method1;  

EDIT

В другом случае можно утверждать, что если FreeAndNil не используется, код также не будет аварийно завершаться. Поскольку даже если объект уничтожен, память может не использоваться повторно, и все структуры могут быть в такте. Приведенный выше код может даже работать без проблем, если Free используется для уничтожения PropertyA вместо FreeAndNil.

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

Так что, хотя я согласен с аргументом, что он может скрыть реальный недостаток дизайна и лично не использовать FreeAndNil в деструкторах, обнаружение таких недостатков дизайна - не волшебная пуля.

7 голосов
/ 18 августа 2010

Эту проблему довольно легко объяснить, и спор вокруг этой проблемы скорее субъективен, чем объективен.Использование FreeAndNil просто не нужно, если ссылка на переменную для освобождаемого объекта выйдет из области видимости:

procedure Test;
var
  LObj: TObject;
begin
  LObj := TObject.Create;
  try
    {...Do work...}
  finally
    //The LObj variable is going out of scope here,
    // so don't bother nilling it.  No other code can access LObj.

    //FreeAndNil(LObj);
    LObj.Free;
  end; 
end;

В приведенном выше фрагменте кода обнуление переменной LObj будет бессмысленным по причинедано.Однако, если переменная объекта может быть создана и освобождена несколько раз в течение времени жизни приложения, тогда возникает необходимость проверить, действительно ли объект создан или нет.Это простой способ проверить, была ли ссылка на объект установлена ​​на nil.Чтобы упростить эту настройку до nil, метод FreeAndNil() освободит ресурсы и установит для вас nil.Затем в коде вы можете проверить, создан ли объект с помощью LObj = nil или Assigned(LObj).

Случай использования .Free или FreeAndNil() в деструкторах объекта - это серая область, но по большей части, .Free должен быть безопасным, и добавление ссылок на подобъекты в деструкторе не требуется.Существуют различные аргументы относительно того, как обращаться с исключениями в конструкторах и деструкторах.

Теперь обратите внимание: если вы предпочитаете выбирать .Free или FreeAndNil() в зависимости от конкретных обстоятельств, описанных выше,это нормально, но учтите, что цена ошибки из-за , а не обнуления ссылки на освобожденный объект, к которой впоследствии осуществляется доступ, может быть очень высокой.Если впоследствии получить доступ к указателю (объект освобожден, но ссылка не установлена ​​в ноль), может случиться, что вам не повезло, и обнаружение повреждения памяти происходит на расстоянии многих строк кода от доступа к ссылке на освобожденный, но не ноль.Исправление такого рода ошибки может занять очень много времени, и да, я знаю, как использовать FastMM.

Поэтому для некоторых людей, включая меня, это стало привычкой (возможно, ленивой) простообнуляйте все указатели объектов, когда они освобождены, даже если обнуление не является строго необходимым.

5 голосов
/ 22 ноября 2011

Я имел тенденцию использовать FreeAndNil довольно часто (по любой причине), но не больше. То, что заставило меня прекратить делать это, не связано с тем, должна ли переменная быть nil впоследствии или нет. Это связано с изменениями кода, особенно с изменениями типов переменных.

Меня несколько раз укусили после изменения типа переменной с TSomething на тип интерфейса ISomething. FreeAndNil не жалуется и продолжает радостно работать над интерфейсной переменной. Это иногда приводило к таинственным сбоям, которые не могли быть немедленно возвращены туда, где это произошло, и потребовалось некоторое время, чтобы найти.

Так что я переключился на звонок Free. И когда я сочту это необходимым, я в явном виде устанавливаю переменную nil.

3 голосов
/ 05 мая 2012

Я разыскал вопрос о переполнении стека, говоря о FreeAndNil и FreeAndInvalidate, чтобы упомянуть, что жизненный цикл разработки безопасности Microsoft рекомендует что-то подобное с FreeAndInvalidate:

В свете приведенной выше рекомендации SDL - и ряда реальных ошибок, связанных с повторным использованием устаревших ссылок на удаленные объекты C ++ ...

Очевидный выбор значения очистки - NULL. Однако в этом есть и недостатки: мы знаем, что большое количество сбоев приложений связано с разыменованием указателя NULL. Выбор NULL в качестве значения очистки будет означать, что новые сбои, вызванные этой функцией, могут с меньшей вероятностью выделять разработчику необходимость правильного решения - то есть правильного управления временем жизни объекта C ++ - вместо простой проверки NULL, которая подавляет немедленный симптом ,

Также проверки на NULL являются общей конструкцией кода, означающей, что существующая проверка на NULL в сочетании с использованием NULL в качестве значения очистки может случайно скрыть подлинную проблему безопасности памяти, первопричину которой действительно нужно устранить.

По этой причине мы выбрали 0x8123 в качестве значения очистки - с точки зрения операционной системы, он находится на той же странице памяти, что и нулевой адрес (NULL), но нарушение доступа в 0x8123 будет лучше выделяться для разработчика как нуждающегося более подробное внимание.

Так в основном:

procedure FreeAndInvalidate(var obj);
var
   temp : TObject;
const 
   INVALID_ADDRESS = $8123; //address on same page as zero address (nil)
begin
   //Note: Code is public domain. No attribution required.
   temp := TObject(obj);
   Pointer(obj) := Pointer(INVALID_ADDRESS);
   temp.Free;
end;
...