Почему я должен использовать 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;