Должен ли я поставить блок try-finally после каждого Object.Create?

У меня есть общий вопрос о наилучшей практике в OO Delphi. В настоящее время я помещаю блоки try-finally в любое место, где я создаю объект, чтобы освободить этот объект после использования (чтобы избежать утечек памяти). Например:

aObject := TObject.Create;
try
  aOBject.AProcedure();
  ...
finally
  aObject.Free;
end;

вместо:

aObject := TObject.Create;
aObject.AProcedure();
..
aObject.Free;

Как вы думаете, это хорошая практика или слишком много накладных расходов? А как насчет производительности?

8 ответов

Решение

На мой взгляд, есть только одна причина, по которой строительство объекта не должно сопровождаться ( или "в", как указывал Мейсон) try / finally блок.

  1. Если время жизни объекта управляется другим объектом.

Это управление может принимать три формы:

  1. Ссылка на объект имеет область действия за пределами локального блока и освобождается в другом месте - как член поля, освобожденный в деструкторе.
  2. Объект немедленно добавляется в список, который отвечает за освобождение объекта позже.
  3. Объект со связанным менеджером времени жизни, например, как вы передаете владельца в элемент управления VCL при создании.

С # 1, когда ссылка имеет более широкую область действия, ссылка должна быть немедленно установлена ​​равной nil, если она не создана сразу. Таким образом, когда он проверяется для справки, вы знаете, что у вас есть точные данные. Это чаще всего встречается с объектами-членами, которые создаются как часть большего класса, а затем очищаются при уничтожении родительского объекта.

С # 2, когда объект добавляется в список, вы хотите использовать try-except block (один из немногих случаев, когда я его использую) на случай, если после создания объекта и до его добавления в список управления возникает исключение. В идеале первая строка после конструкции добавляет его в список, или список на самом деле является фабричным классом, который дает вам объект, уже добавленный в список.

С # 3, когда у объекта есть другой менеджер времени жизни, вы действительно должны убедиться, что управление этим менеджером является правильным решением. Если вы создаете элемент управления VCL, у вас может возникнуть искушение владеть формой (или любым другим элементом управления), но это фактически накладывает дополнительные затраты на конструкцию и разрушение. Если возможно, вы должны явно освободить его, это особенно верно, если вы включаете элемент управления один раз, тогда вы знаете, что освободите его в деструкторе своей формы или когда он закроется. Единственный раз, когда вы не можете сделать это, это если создание элемента управления является более динамичным.

Так что да, лучше всего использовать много try / finally блоки. Вы должны иметь только несколько try / except блоки, и большинство из них должны перехватывать очень специфические типы исключений и / или повторно вызывать исключение. Если у вас есть больше try / except чем try / finally блоки, значит, вы делаете это неправильно.

Это определенно лучшая практика, чтобы использовать try-finally.

В случае возникновения исключения этот объект будет освобожден.

Что касается производительности: измеряйте, прежде чем оптимизировать.

Как сказал Фрэнк, "что касается производительности: измеряйте, прежде чем оптимизировать". Повторяя это, чтобы подчеркнуть.

Кроме того, если вы создаете кучу объектов в методе, вам не нужно использовать блок try..finally для каждого из них. Это может привести к уродливой путанице. create, try, create, try, create, try, do something, finally, free, finally, free, finally, free. Тьфу! Вместо этого вы можете установить ссылки на объекты равными nil в верхней части метода, затем создать их все, сделать один блок try и освободить их все в разделе finally.

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

В прошлом году на днях в Delphi Developer я видел частный тайник кода Марко Канту, и он звонил, наконец, каждый раз, когда он что-то создает.

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

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

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

Чтобы ответить на вторую часть вашего вопроса:
try finally вряд ли есть какие-либо накладные расходы.

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

--jeroen

Если вы создаете объект в конструкторе класса, и объект будет принадлежать включающему классу, вы захотите освободить его в деструкторе класса-владельца.

Я склонен использовать FreeAndNil() вместо вызова Free.

РЕДАКТИРОВАТЬ: Но, как говорили другие, вы определенно всегда хотите освободить то, что вы создаете.

Да, это всегда хорошая идея (существенная), если код, который создает объект, отвечает за его освобождение. Если нет, то попытка / наконец-то не подходит, но опять же, ни один.

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

С AutoFree() ваш код может быть написан так:

aObject := TObject.Create;
AutoFree(@aObject);

aObject.AProcedure();

или, альтернативно, поскольку в реализации используется ссылка на ссылку (для включения автоматического NIL'а, а также Free'ing), вы можете даже предварительно зарегистрировать свои ссылки, которые вы хотите использовать в качестве AutoFree, чтобы хранить такие служебные декларации подальше. исходя из вашей бизнес-логики и сохраняйте реальное "мясо" вашего кода как можно более чистым (это особенно полезно, когда потенциально многократные объекты должны быть Free'd):

AutoFree(@aObject1);
AutoFree(@aObject2);

aObject1 := TObject.Create;
aObject1.AProcedure();

// Later on aObject2 is (or may be) also created
 :

В моем исходном сообщении не показано более позднее дополнение к механизму поддержки регистрации нескольких ссылок в одном вызове AutoFree(), но я уверен, что вы можете самостоятельно выяснить, какие изменения необходимы для поддержки этого, если вы хотите в состоянии сделать это:

AutoFree([@aObject1, @aObject2]);

aObject1 := TObject.Create;
aObject1.AProcedure();

// Later on aObject2 is (or may be) also created
 :

Даже если это очень рекомендуется сделать, я не всегда буду это делать.

Мои правила использования или не использования try/finally:

  • Вероятность того, что этот объект упадет и сгорит.
  • Сколько раз объект создавался. Если я знаю, что ваш объект создается редко (не более 5-6 раз за время жизни приложения), я могу жить с утечкой памяти в 20 КБ - в случае, если он "умирает", но не освобождается.
  • Объем утечки памяти в случае сбоя объекта.
  • Сложность кода. Попробуйте / кроме того, чтобы код выглядел действительно ужасно. Если есть процедура из 5 строк, я всегда использую try / исключением.
  • Размер файла приложения. Если ваше приложение должно работать в течение нескольких дней, то я определенно хочу освободить ЛЮБОЙ фрагмент памяти, иначе утечка будет накапливаться.

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

Другие вопросы по тегам