【问题标题】:OOP equivalent for a goto chain for releasing resources upon error?用于在错误时释放资源的 goto 链的 OOP 等效项?
【发布时间】:2018-11-05 07:39:22
【问题描述】:

在 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 和反对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;

这真的像看起来那么糟糕吗?将gotoexcept 混合在一起似乎更糟,到目前为止我能想到的只有这些。

【问题讨论】:

  • goto 通常被认为是邪恶的,如果有的话,应该很少使用它。对于 C++,更好的解决方案是改用 RAII,然后在堆栈上创建对象,并让它们的析构函数在对象超出范围时释放资源。不过,C 和 Delphi 没有 RAII 或带有析构函数的基于堆栈的对象。在 Delphi 中,您可以使用接口模拟 RAII。

标签: oop delphi memory-management exception-handling


【解决方案1】:

您的AllocStuff() 应该使用try/except 来捕获错误,因此它不会返回无效数据:

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() 返回的任何内容:

procedure QuestionableControlFlow;
var
  A : TA;
begin
  A := AllocStuff;
  try
    DoSomethingWith(A);
  finally
    Dispose(A.C);
    Dispose(A.B);
    Dispose(A);
  end;
end;

【讨论】:

  • 嵌套成员应由主类型在其 Destroy 方法中处理。手动 Dispose() 是错误的(即使它工作,正确编码),并且破坏了封装的最简单的好处。正如大卫在他的回答中所写,只需正确定义对象及其嵌套成员,然后通过 try/finally 使用它来保护资源。
  • @Arnaud,您对封装是正确的,但对于问题示例,此答案正确考虑了分配器(构造函数)中的异常并正确释放嵌套分配的资源。对于这种情况,您不能使用try..finally,因为嵌套成员必须在分配器返回时保持分配状态。
  • @Victoria Asker 认为 goto 对此有效,因此我不会对 Q 中显示的技术感到困惑。
【解决方案2】:

在 Delphi 中,您使用 try/finally 来表示非托管资源的生命周期。

例如

obj := TObject.Create;
try
  obj.DoSomething;
finally
  obj.Free;
end;

您绝对不要为此使用try/except,尽管这是一个常见错误。这是为了处理与保证最终确定不同的异常。

当您需要在一个函数中处理多个非托管资源时,您可以嵌套 try/finally 块。当嵌套很深时,可能会很混乱。可以在这里找到一些处理这个问题的想法:Avoiding nested try...finally blocks in Delphi

【讨论】:

  • 如果我可以在这个答案中加上 +10,我会的! :)
猜你喜欢
  • 2018-11-08
  • 1970-01-01
  • 2019-12-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-03-24
相关资源
最近更新 更多