Привет. Как лучше всего выполнять вложенные операторы 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;
Можете ли вы предложить лучший способ сделать это?
Ааааа поездка по переулку памяти.
Я знаю, опоздал на 6 месяцев, но технически я думаю, что это правильный и безопасный способ. Все предложенные ответы имеют возможные утечки или требуют подпрограмм резервного копирования / поддержки. Этот код будет работать без утечек.
imho нет лучшего способа - не следует использовать только конструктор, отличный от nil.





как насчет следующего:
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 не удалось. Только строки и указатели интерфейсов могут считаться пустыми при использовании в качестве переменных стека.
Вам не нужны проверки «if Assigned (cdsX)», FWIW.
Также обратите внимание, что этот код уязвим для утечек памяти, если TClientDataSet.Destroy () вызывает исключения. Смотрите мой собственный ответ на код, который также можно написать, чтобы обезопасить себя от этого.
Это действительно ужасно! Довольно сложно уйти от лучших практик:
1. Create всегда нужно помещать перед try / finally-free в случае исключений в конструкторе - инициализация nil для предотвращения этого - всего лишь уродливый взлом. 2. вы либо передаете Владельца, отличного от nil, для создания, либо освобождаете объект самостоятельно, но не сразу!
@Oliver: Опять же, передача владельца, отличного от nil, при создании компонентов не предотвращает ручное удаление - VCL обрабатывает этот случай без каких-либо проблем. Для доказательства см. Источники TComponent.Destroy () и TComponent.Notification ().
@Oliver, суть в том, что если в одном из созданий возникает исключение, автоматически вызывать освобождение любого из подобъектов и устранять вложение.
@mghie: Это действительно не мешает этому, так что на самом деле это не «неправильно», но весь механизм управления владельцем добавляет много совершенно ненужных накладных расходов.
Единственное, что я бы, наверное, изменил, - это переместить 1 создание из Try ... Наконец. Не то чтобы это имеет большое значение, но все же немного более оптимально. Что касается возможных исключений в деструкторах. Сама VCL вряд ли этим заморачивается. Я только что проверил TForm.Destroy (вариант D2010), и он не беспокоит упаковку каждого бесплатного в try..except. Итак, что касается VCL, исключение в деструкторе кажется критическим отказом.
Я справляюсь с этим, используя мою собственную процедуру InitialiseNil, которая принимает несколько параметров, и процедуру FreeAndNil, которая также принимает несколько параметров. Это немного улучшает чтение приведенного выше кода.
Это не хорошо. Если в одном из первых трех освобождений возникает исключение, происходит утечка памяти.
@DavidHeffernan, не могли бы вы рассказать больше об этих подпрограммах InitializeNil / FreeAndNil? Единственное решение, с которым я пришел, - это InitializeNil (objs: array of PObject) и вызывать его с помощью InitializeNil ([@ obj1, @ obj2, ...]), но это выглядит не очень хорошо.
@Yuri: просто имейте несколько перегруженных версий InitializeNil, одну с одним параметром, одну с двумя и т. д. В любом случае вам, вероятно, не понадобится больше трех или четырех параметров.
Я бы использовал что-то вроде этого:
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 для использования, но она, наконец, полностью исключает попытку, что может быть достаточно хорошим для этого примера.
Я вижу попытку ... наконец, это не более чем костыль, необходимый, потому что RAII невозможен в Delphi без интерфейсов. Если бы в Delphi были объекты, размещенные в стеке, не было бы особой нужды пытаться ... наконец. Тем не менее, его можно использовать вместе с интерфейсом SafeGuard.
@mghie: За исключением случаев, когда деструктор закрывает дескриптор или освобождает некоторые другие ресурсы, что довольно часто.
@ себя: я не слежу. Деструкторы никогда не должны генерировать исключения, поскольку управление владением компонентами VCL небезопасно для исключений. Итак, если будут вызваны все деструкторы, что вы имеете в виду под «кроме случаев, когда ...»?
@mghie: Если их назовут, то да. Думал, ты говоришь о том, чтобы просто раскрутить стек «на всякий случай». Кстати, деструкторы не могут «никогда не генерировать исключения». Вы не всегда знаете, когда будет сгенерировано исключение, в конце концов, это исключение.
@himself: Если вы перейдете по ссылке на статью, то увидите, что вызываются деструкторы будут. И хотя вы правы в том, что деструкторы может генерируют исключения, им лучше этого не делать (по крайней мере, в потомках TComponent, которые используются с управлением временем жизни VCL). Утечки памяти и / или сбои являются неизбежным следствием.
@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++ ...
Не используйте предметы старого стиля. Они плохо работают с наследованием и не поддерживают какие-либо типы Delphi, представленные в последнее десятилетие, такие как длинные строки, интерфейсы или динамические массивы.
@ Роб: Что вы имеете в виду под предметами старого стиля? Для объекта? Какая альтернатива лучшая?
@Luke: объект старого стиля создается с помощью TMyObject = object вместо TMyObject = class или даже TMyObject = class (TObject). Объекты старого стиля давно устарели и существуют только для обратной совместимости.
Есть еще один вариант кода без вложенной попытки ... наконец, это только что пришло мне в голову. Если вы не создаете компоненты с параметром 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 работает так же хорошо.
Однако согласился со вторым комментарием. Не освобождать cds1 (т.е. позволить приложению делать это при завершении работы приложения) немного лучше, чем утечка памяти.
Хорошо, но IObjectSafe лучше IMO, потому что он обрабатывает TObjects, а также TComponents.
@Roddy: Я полностью согласен. С такими интерфейсами можно сделать гораздо больше, я начал использовать их постоянно (например, для установки и сброса курсоров занятости, блокировки и разблокировки объектов синхронизации, временного отключения обновлений и многого другого). Качественный товар.
Есть хорошее видео на исключения в конструкторах и деструкторах
Он показывает несколько хороших примеров, таких как:
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: Ознакомьтесь с обработкой отложенных исключений, на которую он ссылается. Я написал его специально для обработки исключений в деструкторах, поскольку, когда код является сторонним, вы не всегда можете его изменить.
@Jim: Верно, и VCL не исключение, это третья сторона, которую вы не можете изменить. Я не знаю о последней версии VCL, но поскольку управление владением VCL исторически никогда не было безопасным для исключений, было бы не лучшим решением, чтобы деструкторы компонентов генерировали исключения. Если компоненты принадлежат форме и освобождены VCL, это приведет к утечкам памяти и / или сбоям. Ваш код также не помогает с собственными компонентами.
Если вы хотите пойти по этому (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;
Прежде всего: не передавайте в Create значение Владельца, отличное от nil, если вы все равно собираетесь освобождать объекты самостоятельно. Это просто добавляет много ненужных накладных расходов.