2010-08-18 2 views
11

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

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

+0

Вы задали хороший вопрос здесь. – Ampere

ответ

14

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

+0

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

+4

Для защиты вы можете использовать что-то вроде «FreeAndInvalidate» (http://delphitools.info/2010/02/06/dont-abuse-freeandnil-anymore/), чтобы сделать ссылку недействительной, а не только нулевой, так что все последующие действия для доступа к объекту будет инициировано исключение. –

+0

Спасибо за ссылку. Это дает мне некоторое представление. –

7

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

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

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

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

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

+0

Вы рассматриваете единственный случай, когда у вас есть крошечная процедура, в которой переменная выходит за рамки. Это больше, чем это. Но +1, я согласен с использованием FreeAndNil. – Ampere

+0

@Altar: Я выбрал этот пример, потому что он обеспечивает наилучшую ситуацию, в которой можно утверждать, что «Свободный» лучше, чем «FreeAndNil». Все другие ситуации менее точны. Повторить: I * всегда * nil мои ссылки на объекты при уничтожении экземпляров объектов, так что мне никогда не нужно думать о том, нужно мне это или нет. –

5

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

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

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

3

я выследил на StackOverflow вопрос говорить о FreeAndNil и FreeAndInvalidate отметить, что жизненный цикл разработки безопасности Microsoft в настоящее время recommends something similar to 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; 
Смежные вопросы