我可以在不调用隐藏的 try-finally 的情况下使用接口吗

Posted

技术标签:

【中文标题】我可以在不调用隐藏的 try-finally 的情况下使用接口吗【英文标题】:Can I use interfaces without invoking hidden try-finally's 【发布时间】:2015-04-26 19:11:34 【问题描述】:

我想refactor DelphiAST使用接口来处理不同的类型,而不是笨拙的 它现在使用的 TDirectionary。

Some research 表明 70% 以上的运行时间都花在了字典上。

所以我会做这样的接口:

TSyntaxNode = class
  ...
end;

IIdentifier = interface
  ['D72E945D-E397-4E02-B702-8AE6C235F9C6']
  function GetIdentifier: string;
  property Identifier: string read GetIdentifier;
end;

IMethod = interface(Identifier)
  ['8E6119DC-E0F3-42BD-A5BF-FB658E34499E']
  .....
end;

TMethodNode = class(TSyntaxNode, IMethod, IIdentifier,...)
 ...
end;

根据 Roman 的问题是:

引用计数可能会导致性能问题。 DelphiAST 创建了数千个类来生成语法树(当输入文件足够大时,超过 100,000 个 TSyntaxNode 实例)。引用计数器会被调用多少次?

每次发生这种情况时都会调用隐藏的try finally,这会减慢速度。

在方法参数中严格使用const 可防止引用计数代码调用该方法,但是每次您执行诸如MyRef = List[0] 之类的操作时它仍然会发生 - 它会增加分配给MyRef 的引用计数,甚至尽管该项目仍然存在于列表中。

如何使用接口而不用担心 refcounting 和 try-finally 块? 我非常乐意手动管理类的破坏。

更多信息 我猜我需要使用 TAggregatedObject 作为基础祖先。 我在某处读到,不分配 GUID 会抑制引用计数,但必须提供资源来支持它。 但是丢失 GUID 会导致获取子接口时出现问题,所以我必须为此设计一个解决方案....

【问题讨论】:

这里有一些关于如何使用记录来实现接口的信息:sergworks.wordpress.com/2012/04/01/interfaces-without-objects 但我希望接口的接口部分消失,所以 try-finally 消失了. MyRef 声明为Pointer。赋值语句将起作用。当您需要访问接口的方法时,将其类型转换为所需的接口类型。类型转换不会产生引用计数。 您的优化方向错误。在 DelphiAST 中优化代码的方法是使用多态,因为这就是它的用途。 为什么 DelphiAST 甚至使用泛型? @SilverWarior,泛型不会导致缓慢,可能是代码膨胀,但不会导致缓慢。 【参考方案1】:

我可以在不调用隐藏的 try-finally 的情况下使用接口吗?

没有。无论如何,编译器都会发出带有接口的引用计数代码。你无法避免它。

您可以使用函数指针记录实现您自己的接口版本。它会更笨重,但会避免堆分配和引用计数。

【讨论】:

是的,可以,我记得在一个 delphi 博客中看到过类似的东西,但不记得在哪里。 只需声明一个记录,其成员是函数指针。手动实现的接口。【参考方案2】:

“万千物件”总是让我不寒而栗。内存中的对象有很大的开销。您忘记了它,但是当您尝试管理数千个时,它会再次弹出,或者注意到您的性能下降,或者开始尝试从文件中写入或读取...

据我所知,使用接口不会有太大变化,因为您仍然在下面使用对象(类实例)。

这种规模的努力需要专门使用古老的直接内存数据结构。例如,我一直在使用存储在记录数组中的 AST:https://github.com/stijnsanders/strato

【讨论】:

这并不是问题的真正答案,但我很好奇您的解决方案。顺便说一句,当前形式的 DelphiAST 的缓慢是由于使用了 TDictionary,而不是主要由于创建了太多 TSyntaxNode 实例。 您使用记录数组,它们携带与接口完全相同的引用计数问题。但不支持多态性,因此虽然有趣,但对于解析结构复杂的 Delphi 代码无济于事。 嗯,我不知道有这样的需求。现在我觉得我应该自己尝试一下。一个项目的所有单元和一个符号表...(然后继续弄清楚这个LLVM-IR的东西...)【参考方案3】:

不,你可以不调用try-finally和引用计数就不能使用接口。 但是,您可以大大减少隐藏异常处理程序的数量。 你只需要非常小心地做两件事。

    在传递接口时始终使用const 参数。

    永远不要将接口存储在接口类型变量中,而是使用自制记录来封装接口,这样它的引用计数就不会被触及。

以下是封装记录的示例:

type
  TInterface<Intf: IInterface> = record
  private
    P: Pointer;
  public
    function I: Intf; inline;
    class operator Implicit(const A: Intf): TInterface<Intf>; inline;
  end;

function TInterface<Intf>.I: Intf;
begin
  pointer(IInterface(Result)):= P;
end;

class operator TInterface<Intf>.Implicit(const A: Intf): TInterface<Intf>;
begin
  Result.P:= pointer(IInterface(A));
end;

这是一个演示该概念的示例程序。

program Project32;
$APPTYPE CONSOLE

$R *.res
uses
  System.SysUtils;

type
  TInterface<Intf: IInterface> = record
  private
    P: Pointer;
  public
    function I: Intf; inline;
    class operator Implicit(const A: Intf): TInterface<Intf>; inline;
  end;

  ITest1 = interface
    function Test1: integer;
  end;

  ITest2 = interface
    function Test2: integer;
  end;

  TTest = class(TAggregatedObject, ITest1, ITest2)
    function Test1: integer;
    function Test2: integer;
  end;

 TTest 

function TTest.Test1: integer;
begin
  Result:= 1;
end;

function TTest.Test2: integer;
begin
  Result:= 2;
end;

 TInterface<Intf> 

function TInterface<Intf>.I: Intf;
begin
  pointer(IInterface(Result)):= P;
end;

class operator TInterface<Intf>.Implicit(const A: Intf): TInterface<Intf>;
begin
  Result.P:= pointer(IInterface(A));
end;

var
  I1: TInterface<ITest1>;
  I2: TInterface<ITest2>;
  Test: TTest;

begin
  Test:= TTest.Create(nil);  //Force AV on _AddRef, _Release
  If (Test.Test1 = 1) then WriteLn(S);
  I1:= Test;
  If (I1.I.Test1 =1) then WriteLn(S);
  I2:= Test;
  If (I2.I.Test2 = 2) then WriteLn(S);
  ReadLn(s);
  Test.Free;
end.

TAggregatedObject 没有处理_AddRef/_Release 调用的接口。 在程序的生命周期中,不会发生任何问题,但是 Delphi 确实将 TTest 的创建包装在 try-finally 中,这将在退出函数时产生异常。

在实际使用中,您必须使用 TInterfacedObject。如果您通过很多接口引用它可能会有所帮助。

【讨论】:

以上是关于我可以在不调用隐藏的 try-finally 的情况下使用接口吗的主要内容,如果未能解决你的问题,请参考以下文章

tryresources优于try-finally

第9项:尽量使用try-with-resources而不是try-finally(Prefer try-with-resources to try-finally)

在不修改快捷方式的情况下使批处理文件隐藏/最小化

如何将此 try-finally 更改为 try-with-resources?

try-finally的时候try里面带return

可以在不删除“使用”的情况下从 Intellisense 隐藏 Linq 和其他扩展吗?