错误时释放资源的 goto 链的 OOP 等价物?
OOP equivalent for a goto chain for releasing resources upon error?
在 C 语言中,我按照建议使用 goto 链来在出错时释放资源 here。使用 Delphi 我遇到了以下情况,我想优雅地处理内存耗尽并防止内存泄漏:
New(A);
A.DoSomething;
New(A.B);
A.B.DoSomething;
New(A.C);
A.C.DoSomething;
据我了解,检查内存耗尽的方法是捕获 New
抛出的异常。假设 DoSomething
函数都在出错时抛出 Exception
。 SEI CERT 的编码标准不推荐 in-band error checking and against using exceptions for control flow,至少 Java,我觉得非常合理。我不确定如何处理这种情况,同时牢记这些建议。我的想法是做类似
的事情
function AllocStuff : TA;
begin
New(Result);
Result.B := nil;
Result.C := nil;
Result.DoSomething;
New(Result.B);
Result.B.DoSomething;
New(Result.C);
Result.C.DoSomething;
end;
在调用者上捕获异常:
procedure QuestionableControlFlow;
var
A : TA;
begin
A := nil;
try
A := AllocStuff;
DoSomethingWith(A);
Dispose(A);
except on E : Exception do
begin
if (A <> nil) then
begin
if (A.B <> nil) then
begin
if (A.C <> nil) then
begin
Dispose(A.C);
end;
Dispose(A.B);
end;
Dispose(A);
end;
end;
end;
这真的像看起来那么糟糕吗?将 goto
与 except
混合使用似乎更糟糕,这是我目前所能想到的。
在 Delphi 中,您将 try/finally
用于非托管资源生命周期。
例如
obj := TObject.Create;
try
obj.DoSomething;
finally
obj.Free;
end;
你绝对不要为此使用 try/except
,尽管这是一个常见的错误。那是为了处理不同于保证最终确定的异常。
当您需要在一个函数中处理多个非托管资源时,您可以嵌套 try/finally
块。当嵌套很深时,可能会很乱。可以在此处找到处理该问题的一些想法:Avoiding nested try...finally blocks in Delphi
您的 AllocStuff()
应该使用 try/except
来捕获错误,因此它不会 return 无效数据:
function AllocStuff : TA;
begin
New(Result);
try
Result.B := nil;
Result.C := nil;
Result.DoSomething;
New(Result.B);
try
Result.B.DoSomething;
New(Result.C);
try
Result.C.DoSomething;
except
Dispose(Result.C);
raise;
end;
except
Dispose(Result.B);
raise;
end;
except
Dispose(Result);
raise;
end;
end;
然后调用者可以使用try/finally
释放任何AllocStuff()
returns:
procedure QuestionableControlFlow;
var
A : TA;
begin
A := AllocStuff;
try
DoSomethingWith(A);
finally
Dispose(A.C);
Dispose(A.B);
Dispose(A);
end;
end;
在 C 语言中,我按照建议使用 goto 链来在出错时释放资源 here。使用 Delphi 我遇到了以下情况,我想优雅地处理内存耗尽并防止内存泄漏:
New(A);
A.DoSomething;
New(A.B);
A.B.DoSomething;
New(A.C);
A.C.DoSomething;
据我了解,检查内存耗尽的方法是捕获 New
抛出的异常。假设 DoSomething
函数都在出错时抛出 Exception
。 SEI CERT 的编码标准不推荐 in-band error checking and against using exceptions for control flow,至少 Java,我觉得非常合理。我不确定如何处理这种情况,同时牢记这些建议。我的想法是做类似
function AllocStuff : TA;
begin
New(Result);
Result.B := nil;
Result.C := nil;
Result.DoSomething;
New(Result.B);
Result.B.DoSomething;
New(Result.C);
Result.C.DoSomething;
end;
在调用者上捕获异常:
procedure QuestionableControlFlow;
var
A : TA;
begin
A := nil;
try
A := AllocStuff;
DoSomethingWith(A);
Dispose(A);
except on E : Exception do
begin
if (A <> nil) then
begin
if (A.B <> nil) then
begin
if (A.C <> nil) then
begin
Dispose(A.C);
end;
Dispose(A.B);
end;
Dispose(A);
end;
end;
end;
这真的像看起来那么糟糕吗?将 goto
与 except
混合使用似乎更糟糕,这是我目前所能想到的。
在 Delphi 中,您将 try/finally
用于非托管资源生命周期。
例如
obj := TObject.Create;
try
obj.DoSomething;
finally
obj.Free;
end;
你绝对不要为此使用 try/except
,尽管这是一个常见的错误。那是为了处理不同于保证最终确定的异常。
当您需要在一个函数中处理多个非托管资源时,您可以嵌套 try/finally
块。当嵌套很深时,可能会很乱。可以在此处找到处理该问题的一些想法:Avoiding nested try...finally blocks in Delphi
您的 AllocStuff()
应该使用 try/except
来捕获错误,因此它不会 return 无效数据:
function AllocStuff : TA;
begin
New(Result);
try
Result.B := nil;
Result.C := nil;
Result.DoSomething;
New(Result.B);
try
Result.B.DoSomething;
New(Result.C);
try
Result.C.DoSomething;
except
Dispose(Result.C);
raise;
end;
except
Dispose(Result.B);
raise;
end;
except
Dispose(Result);
raise;
end;
end;
然后调用者可以使用try/finally
释放任何AllocStuff()
returns:
procedure QuestionableControlFlow;
var
A : TA;
begin
A := AllocStuff;
try
DoSomethingWith(A);
finally
Dispose(A.C);
Dispose(A.B);
Dispose(A);
end;
end;