Delphi 执行嵌套TRY/FINALLY语句的最佳实践
您好,在delphi中执行嵌套的try&finally语句的最佳方法是什么Delphi 执行嵌套TRY/FINALLY语句的最佳实践,delphi,memory-management,object-lifetime,try-finally,Delphi,Memory Management,Object Lifetime,Try Finally,您好,在delphi中执行嵌套的try&finally语句的最佳方法是什么 var cds1 : TClientDataSet; cds2 : TClientDataSet; cds3 : TClientDataSet; cds4 : TClientDataSet; begin cds1 := TClientDataSet.Create(application ); try cds2 := TClientDataSet.Crea
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;
你能推荐一种更好的方法吗?以下几点如何:
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;
这使它保持紧凑,并且仅尝试释放已创建的实例。实际上不需要执行嵌套,因为任何失败都会导致跳转到finally并执行您提供的示例中的所有清理
就我个人而言,我尽量不使用相同的方法进行嵌套。。。例外情况是try/try/except/finally场景。如果我发现自己需要嵌套,那么对我来说,这是一个考虑重构到另一个方法调用的好时机
编辑得益于和的评论,这篇文章有点干净了
EDIT将对象创建更改为“不引用应用程序”,因为在本例中不需要这样做。我将使用以下内容:
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;
var
MyObject: TMyObject;
begin
MyObject.Init;
try
/// ...
finally
MyObject.Done;
end;
end;
有关接口的实现,请参阅本文,但您可以自己轻松创建类似的东西
编辑:
我刚刚注意到,在链接的文章中,Guard()是一个过程。在我自己的代码中,我重载了返回TObject的Guard()函数,上面的示例代码假设了类似的情况。当然,对于泛型,更好的代码现在是可能的
编辑2:
如果你想知道为什么要尝试。。。最后在我的代码中完全删除了:如果不引入内存泄漏(当析构函数引发异常时)或访问冲突的可能性,则不可能删除嵌套块。因此,最好使用helper类,让接口的引用计数完全接管。helper类可以释放它所保护的所有对象,即使某些析构函数引发异常。@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
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;
var
MyObject: TMyObject;
begin
MyObject.Init;
try
/// ...
finally
MyObject.Done;
end;
end;
好的,我承认,这几乎是离题了,但我认为在这种情况下可能会很有趣,因为堆栈分配的对象是作为一种解决方案提到的(如果没有自动析构函数调用,它们就不是这样)。没有嵌套try的代码还有另一种变体。。。我终于想到了。如果您不创建构造函数的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;
我非常相信小代码(如果它不是太模糊的话)。有一个很好的视频
它显示了一些很好的示例,例如:
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不会被销毁
编辑
另一个好资源是:
Jim McKeeth在代码范围III中的出色视频,他在finally块中谈到了处理异常的问题。如果你想走这条(IMO)丑陋的路线(初始化为零的组处理,以了解是否需要释放),您至少必须保证不会让某个析构函数中的异常阻止释放其余对象。比如:
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;
您需要使用nil初始化cds2、cds3和cds4,否则,如果创建cds1失败,在释放(无效)对象时将获得AVs。当用作堆栈变量时,只能假设字符串和接口指针为空。这真是太糟糕了!很难再远离最佳实践:1。如果构造函数中出现异常,Create必须始终放在try/finally-free之前-初始化为nil以防止出现这种情况,这只是一个丑陋的攻击。2.您可以传递一个非零所有者来创建对象,也可以自己释放对象-决不能两者都传递!我使用自己的InitialiseNil例程处理这个问题,该例程接受多个参数,而FreeAndNil例程也接受多个参数。这会使上面的代码读得更好一些。这不好。如果前三次释放中的一次出现异常,则内存泄漏。很好的实现,+1供使用,但它完全消除了try finally,这对于本例来说可能已经足够好了。我看到try。。。最后,只需要一根拐杖,因为在Delphi中,没有接口,RAII是不可能实现的。如果Delphi有堆栈分配的对象,那么就不需要尝试了。。。最后尽管如此,它仍然可以与保护接口一起使用。@mghie:除非析构函数关闭句柄或释放一些其他资源,这是非常常见的情况。@mghie:如果它们将被调用,那么是的。我还以为你们说的是“万一发生什么事”就把堆栈展开。另一方面,析构函数不能“从不抛出异常”。你并不总是知道什么时候会抛出异常,毕竟这是一个异常。@hiver:如果你按照文章的链接,你会看到析构函数将被调用。虽然析构函数可以抛出异常,但最好不要这样做(至少在与VCL生存期管理一起使用的
TComponent
子体中)。内存泄漏和/或崩溃是不可避免的后果。抱歉,我不太清楚。自动析构函数调用正是我想要的,不管实例是堆栈分配的,还是超出范围的,或者它是否包含在另一个对象中,并且在父对象被销毁时调用所包含对象的析构函数。正如C++一样,不要使用旧的样式对象。它们不能很好地处理继承,并且不支持过去十年中引入的任何Delphi类型,例如长字符串、接口或动态数组。@Rob:旧的stlye对象是什么意思?托宾?什么是最好的选择?@Luke:一个老式的对象是用TMyObject=object创建的,而不是用TMyObject=class甚至TMyObject=class(TObject)。旧式对象已被弃用很长一段时间了,它们只是t