Почему я должен использовать Free, а не FreeAndNil в деструкторе?

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

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

Ответ 1

Проблема заключается в том, что многие похоже, используют 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;  

ИЗМЕНИТЬ

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

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

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

Ответ 2

Вопрос достаточно прост для объяснения, и спор вокруг этой проблемы более субъективен, чем объективный. Использование 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.

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

Ответ 3

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

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

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

Ответ 4

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

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

Очевидным выбором значения санитарии является NULL. Однако есть и недостатки: мы знаем, что большое количество сбоев приложений связано с различиями в NULL-указателях. Выбор NULL в качестве значения санитарии означал бы, что новые сбои, введенные этой функцией, могут быть менее вероятными для разработчиков, поскольку они нуждаются в правильном решении - то есть правильном управлении сроками жизни С++, а не просто проверке 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;