Лучшая практика для выполнения вложенного оператора TRY / FINALLY

Привет. Как лучше всего выполнять вложенные операторы try & finally в delphi?

var cds1  : TClientDataSet;
    cds2  : TClientDataSet;
    cds3  : TClientDataSet;
    cds4  : TClientDataSet;
begin
  cds1      := TClientDataSet.Create(application );
  try
    cds2      := TClientDataSet.Create(application );
    try
      cds3      := TClientDataSet.Create(application );
      try
        cds4      := TClientDataSet.Create(application );
        try
        ///////////////////////////////////////////////////////////////////////
        ///      DO WHAT NEEDS TO BE DONE
        ///////////////////////////////////////////////////////////////////////
        finally
          cds4.free;
        end;

      finally
        cds3.free;
      end;
    finally
      cds2.free;
    end;
  finally
    cds1.free;
  end;
end;

Можете ли вы предложить лучший способ сделать это?

Прежде всего: не передавайте в Create значение Владельца, отличное от nil, если вы все равно собираетесь освобождать объекты самостоятельно. Это просто добавляет много ненужных накладных расходов.

Oliver Giesen 31.12.2008 15:03

Ааааа поездка по переулку памяти.

Dave Van den Eynde 05.02.2009 17:01

Я знаю, опоздал на 6 месяцев, но технически я думаю, что это правильный и безопасный способ. Все предложенные ответы имеют возможные утечки или требуют подпрограмм резервного копирования / поддержки. Этот код будет работать без утечек.

Despatcher 20.06.2009 01:09

imho нет лучшего способа - не следует использовать только конструктор, отличный от nil.

mjn 25.03.2010 19:40
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
20
4
5 626
6
Перейти к ответу Данный вопрос помечен как решенный

Ответы 6

Ответ принят как подходящий

как насчет следующего:

var cds1  : TClientDataSet;
    cds2  : TClientDataSet;
    cds3  : TClientDataSet;
    cds4  : TClientDataSet;
begin
  cds1      := Nil;
  cds2      := Nil;
  cds3      := Nil;
  cds4      := Nil;
  try
    cds1      := TClientDataSet.Create(nil);
    cds2      := TClientDataSet.Create(nil);
    cds3      := TClientDataSet.Create(nil);
    cds4      := TClientDataSet.Create(nil);
    ///////////////////////////////////////////////////////////////////////
    ///      DO WHAT NEEDS TO BE DONE
    ///////////////////////////////////////////////////////////////////////
  finally
    freeandnil(cds4);
    freeandnil(cds3);
    freeandnil(cds2);
    freeandnil(Cds1);
  end;
end;

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

Лично я стараюсь не вкладываться в один и тот же метод ... за исключением сценария try / try / except / finally. Если мне нужно выполнить вложенность, то для меня самое время подумать о рефакторинге в вызов другого метода.

РЕДАКТИРОВАТЬ Немного подчистили благодаря комментариям mghie и утку.

РЕДАКТИРОВАТЬ изменил создание объекта, чтобы не ссылаться на приложение, поскольку в этом примере это не обязательно.

Вам необходимо инициализировать cds2, cds3 и cds4 с помощью nil, иначе вы получите AV при освобождении (недействительных) объектов, если создание cds1 не удалось. Только строки и указатели интерфейсов могут считаться пустыми при использовании в качестве переменных стека.

mghie 29.12.2008 20:30

Вам не нужны проверки «if Assigned (cdsX)», FWIW.

utku_karatas 29.12.2008 20:34

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

mghie 29.12.2008 20:53

Это действительно ужасно! Довольно сложно уйти от лучших практик:

Oliver Giesen 31.12.2008 14:57

1. Create всегда нужно помещать перед try / finally-free в случае исключений в конструкторе - инициализация nil для предотвращения этого - всего лишь уродливый взлом. 2. вы либо передаете Владельца, отличного от nil, для создания, либо освобождаете объект самостоятельно, но не сразу!

Oliver Giesen 31.12.2008 14:58

@Oliver: Опять же, передача владельца, отличного от nil, при создании компонентов не предотвращает ручное удаление - VCL обрабатывает этот случай без каких-либо проблем. Для доказательства см. Источники TComponent.Destroy () и TComponent.Notification ().

mghie 31.12.2008 16:10

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

skamradt 31.12.2008 16:21

@mghie: Это действительно не мешает этому, так что на самом деле это не «неправильно», но весь механизм управления владельцем добавляет много совершенно ненужных накладных расходов.

Oliver Giesen 31.12.2008 16:51

Единственное, что я бы, наверное, изменил, - это переместить 1 создание из Try ... Наконец. Не то чтобы это имеет большое значение, но все же немного более оптимально. Что касается возможных исключений в деструкторах. Сама VCL вряд ли этим заморачивается. Я только что проверил TForm.Destroy (вариант D2010), и он не беспокоит упаковку каждого бесплатного в try..except. Итак, что касается VCL, исключение в деструкторе кажется критическим отказом.

Ken Bourassa 02.09.2010 00:41

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

David Heffernan 07.01.2011 19:59

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

Pieter B 31.07.2012 18:29

@DavidHeffernan, не могли бы вы рассказать больше об этих подпрограммах InitializeNil / FreeAndNil? Единственное решение, с которым я пришел, - это InitializeNil (objs: array of PObject) и вызывать его с помощью InitializeNil ([@ obj1, @ obj2, ...]), но это выглядит не очень хорошо.

Yuriy Afanasenkov 15.05.2016 22:11

@Yuri: просто имейте несколько перегруженных версий InitializeNil, одну с одним параметром, одну с двумя и т. д. В любом случае вам, вероятно, не понадобится больше трех или четырех параметров.

Rudy Velthuis 03.09.2017 23:10

Я бы использовал что-то вроде этого:

var
  Safe: IObjectSafe;
  cds1 : TClientDataSet;
  cds2 : TClientDataSet;
  cds3 : TClientDataSet;
  cds4 : TClientDataSet;
begin
  Safe := ObjectSafe;
  cds1 := Safe.Guard(TClientDataSet.Create(nil)) as TClientDataSet;
  cds2 := Safe.Guard(TClientDataSet.Create(nil)) as TClientDataSet;
  cds3 := Safe.Guard(TClientDataSet.Create(nil)) as TClientDataSet;
  cds4 := Safe.Guard(TClientDataSet.Create(nil)) as TClientDataSet;
  ///////////////////////////////////////////////////////////////////////
  ///      DO WHAT NEEDS TO BE DONE
  ///////////////////////////////////////////////////////////////////////

  // if Safe goes out of scope it will be freed and in turn free all guarded objects
end;

Для реализации интерфейса см. Статью это, но вы можете легко создать что-то подобное самостоятельно.

Обновлено:

Я только что заметил, что в связанной статье Guard () - это процедура. В моем собственном коде я перегружал функции Guard (), которые возвращают TObject, приведенный выше пример кода предполагает нечто подобное. Конечно, с дженериками теперь возможен гораздо лучший код ...

Обновлено еще раз:

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

Хорошая реализация, +1 для использования, но она, наконец, полностью исключает попытку, что может быть достаточно хорошим для этого примера.

skamradt 29.12.2008 20:42

Я вижу попытку ... наконец, это не более чем костыль, необходимый, потому что RAII невозможен в Delphi без интерфейсов. Если бы в Delphi были объекты, размещенные в стеке, не было бы особой нужды пытаться ... наконец. Тем не менее, его можно использовать вместе с интерфейсом SafeGuard.

mghie 29.12.2008 20:50

@mghie: За исключением случаев, когда деструктор закрывает дескриптор или освобождает некоторые другие ресурсы, что довольно часто.

himself 20.10.2010 20:17

@ себя: я не слежу. Деструкторы никогда не должны генерировать исключения, поскольку управление владением компонентами VCL небезопасно для исключений. Итак, если будут вызваны все деструкторы, что вы имеете в виду под «кроме случаев, когда ...»?

mghie 20.10.2010 21:41

@mghie: Если их назовут, то да. Думал, ты говоришь о том, чтобы просто раскрутить стек «на всякий случай». Кстати, деструкторы не могут «никогда не генерировать исключения». Вы не всегда знаете, когда будет сгенерировано исключение, в конце концов, это исключение.

himself 21.10.2010 14:44

@himself: Если вы перейдете по ссылке на статью, то увидите, что вызываются деструкторы будут. И хотя вы правы в том, что деструкторы может генерируют исключения, им лучше этого не делать (по крайней мере, в потомках TComponent, которые используются с управлением временем жизни VCL). Утечки памяти и / или сбои являются неизбежным следствием.

mghie 21.10.2010 14:56

@mghie: Delphi имеет выделенные стеком объекты:

type
  TMyObject = object
  private
    FSomeField: PInteger;
  public
    constructor Init;
    destructor Done; override;
  end;

constructor TMyObject.Init;
begin
  inherited Init;
  New(FSomeField);
end;

destructor TMyObject.Done;
begin
  Dispose(FSomeField);
  inherited Done;
end;

var
  MyObject: TMyObject;

begin
  MyObject.Init;
  /// ...
end;

К сожалению, как показано в приведенном выше примере: объекты, выделенные стеком, не предотвращают утечки памяти.

Таким образом, для этого все равно потребуется вызов деструктора следующим образом:

var
  MyObject: TMyObject;

begin
  MyObject.Init;
  try
    /// ...
  finally
    MyObject.Done;
  end;
end;

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

Извините за то, что не совсем ясно. Автоматический вызов деструктора - это именно то, что мне нужно, независимо от того, выделен ли экземпляр в стек и выходит за пределы области видимости, или содержится ли он в другом объекте, а деструктор содержащихся объектов вызывается при уничтожении родителя. RAII, как это делает C++ ...

mghie 29.12.2008 22:02

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

Rob Kennedy 31.12.2008 01:04

@ Роб: Что вы имеете в виду под предметами старого стиля? Для объекта? Какая альтернатива лучшая?

lukeck 02.01.2009 06:43

@Luke: объект старого стиля создается с помощью TMyObject = object вместо TMyObject = class или даже TMyObject = class (TObject). Объекты старого стиля давно устарели и существуют только для обратной совместимости.

mghie 02.01.2009 11:39

Есть еще один вариант кода без вложенной попытки ... наконец, это только что пришло мне в голову. Если вы не создаете компоненты с параметром AOwner конструктора, установленным в nil, вы можете просто использовать управление временем жизни, которое VCL предоставляет вам бесплатно:

var
  cds1: TClientDataSet;
  cds2: TClientDataSet;
  cds3: TClientDataSet;
  cds4: TClientDataSet;
begin
  cds1 := TClientDataSet.Create(nil);
  try
    // let cds1 own the other components so they need not be freed manually
    cds2 := TClientDataSet.Create(cds1);
    cds3 := TClientDataSet.Create(cds1);
    cds4 := TClientDataSet.Create(cds1);

    ///////////////////////////////////////////////////////////////////////
    ///      DO WHAT NEEDS TO BE DONE
    ///////////////////////////////////////////////////////////////////////

  finally
    cds1.Free;
  end;
end;

Я большой сторонник маленького кода (если он не слишком запутан).

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

mghie 31.12.2008 15:33

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

mghie 31.12.2008 15:35

Хорошо, но IObjectSafe лучше IMO, потому что он обрабатывает TObjects, а также TComponents.

Roddy 06.01.2009 13:25

@Roddy: Я полностью согласен. С такими интерфейсами можно сделать гораздо больше, я начал использовать их постоянно (например, для установки и сброса курсоров занятости, блокировки и разблокировки объектов синхронизации, временного отключения обновлений и многого другого). Качественный товар.

mghie 07.01.2009 00:04

Есть хорошее видео на исключения в конструкторах и деструкторах

Он показывает несколько хороших примеров, таких как:

var cds1  : TClientDataSet;
    cds2  : TClientDataSet;
begin
  cds1      := Nil;
  cds2      := Nil; 
 try
    cds1      := TClientDataSet.Create(nil);
    cds2      := TClientDataSet.Create(nil);    
    ///////////////////////////////////////////////////////////////////////
    ///      DO WHAT NEEDS TO BE DONE
    ///////////////////////////////////////////////////////////////////////
  finally
    freeandnil(cds2);    //// what has if there in an error in the destructor of cds2
    freeandnil(Cds1);
  end;
end;

Что если в деструкторе cds2 ошибка

Cds1 не будет уничтожен

РЕДАКТИРОВАТЬ

Еще один хороший ресурс:

Джим МакКит отличное видео о Отложенная обработка исключений в диапазоне кода III, где он говорит о проблемах с обработкой исключений в блоке finally.

Я уже прокомментировал этот вопрос по принятому вами ответу. Если вы хотите избавиться от вложенных блоков finally, используйте вспомогательный объект, который освобождает все охраняемые объекты. Затем это можно сделать безопасным способом. Обратите внимание, что у TComponent.DestroyObjects () такая же проблема!

mghie 10.01.2009 14:10

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

mghie 10.01.2009 14:30

@mghie: Ознакомьтесь с обработкой отложенных исключений, на которую он ссылается. Я написал его специально для обработки исключений в деструкторах, поскольку, когда код является сторонним, вы не всегда можете его изменить.

Jim McKeeth 28.10.2010 22:31

@Jim: Верно, и VCL не исключение, это третья сторона, которую вы не можете изменить. Я не знаю о последней версии VCL, но поскольку управление владением VCL исторически никогда не было безопасным для исключений, было бы не лучшим решением, чтобы деструкторы компонентов генерировали исключения. Если компоненты принадлежат форме и освобождены VCL, это приведет к утечкам памяти и / или сбоям. Ваш код также не помогает с собственными компонентами.

mghie 28.10.2010 23:47

Если вы хотите пойти по этому (IMO) уродливому маршруту (групповая обработка с инициализацией до nil, чтобы знать, требуется ли освобождение), вы, по крайней мере, ДОЛЖНЫ гарантировать, что вы не позволите исключению в одном из деструкторов предотвратить освобождение остальной части ваши объекты. Что-то вроде:

function SafeFreeAndNil(AnObject: TObject): Boolean;
begin
  try
    FreeAndNil(AnObject);
    Result :=  True;
  except
    Result := False;
  end;
end;

var cds1  : TClientDataSet;
    cds2  : TClientDataSet;
    IsOK1 : Boolean;
    IsOK2 : Boolean;
begin
  cds1      := Nil;
  cds2      := Nil; 
 try
    cds1      := TClientDataSet.Create(nil);
    cds2      := TClientDataSet.Create(nil);    
    ///////////////////////////////////////////////////////////////////////
    ///      DO WHAT NEEDS TO BE DONE
    ///////////////////////////////////////////////////////////////////////
  finally
    IsOk2 := SafeFreeAndNil(cds2);    // an error in freeing cds2 won't stop execution
    IsOK1 := SafeFreeAndNil(Cds1);
    if not(IsOk1 and IsOk2) then
      raise EWhatever....
  end;
end;

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