【问题标题】:Delphi: better design to avoid circular unit reference?Delphi:更好的设计来避免循环单元引用?
【发布时间】:2016-05-11 22:54:00
【问题描述】:

我在 Delphi 10 中有一个三角形网格结构。

出于性能原因,我将网格顶点、三角形面等的数据存储在 TList 的后代中。

我让 TList 为列表中的每个成员进行计算。对于这些计算,我需要访问 TMesh 结构的某些字段。因此,在创建 TMesh 以及随后创建列表期间,我将父 TMesh 分配给列表。我使用 TMesh 的前向声明来做到这一点。请看以下代码:

type
  {forward declaration}
  TMesh=class;

  TVertex=record
    Point: TPoint3D;
    //other fields
  end;

  TVertices=class(TList<TVertex>)
    Mesh: TMesh;
    procedure DoSomethingWithAllVertices; //uses some fields of TMesh
    constructor Create(const AMesh: TMesh);
    //other methods
  end;

  TTriangleFace=record
    Vertices: Array[0..2] of Integer;
    //other fields
  end;

  TTriangleFaces=class(TList<TTriangleFace>)
    Mesh: TMesh;
    procedure DoSomethingWithAllTriangleFaces; //uses some fields of TMesh
    constructor Create(const AMesh: TMesh);
    //other methods
  end;

  TMesh=class(TComponent)
    Vertices: TVertices;
    TriangleFaces: TTriangleFaces;
    constructor Create(AOwner: TComponent);
    //other fields & methods
  end;

implementation

constructor TMesh.Create(AOwner: TComponent);
begin
  inherited;
  Vertices:=TVertices.Create(Self);
  TriangleFaces:=TTriangleFaces.Create(Self);
end;

constructor TVertices.Create(const AMesh: TMesh);
begin
  Mesh:=AMesh;
end;

这很好用。

但是,由于我的项目正在增长,我得到的代码越来越多,我想将列表类分布在单独的单元中。这会导致循环单元引用的问题。

循环单元引用的问题似乎是众所周知的。我检查了可能的解决方案,但找不到似乎适合我的问题的解决方案。有人说,如果你遇到循环单元引用,代码就设计得很糟糕。

如何在改进设计的同时保持较高的计算性能?

解决问题的其他可能性是什么?

非常感谢!

【问题讨论】:

  • 为什么不让TVertices 执行回调而不是直接访问网格?这样TMesh 可以注入该方法,而您绕过TVertices 需要知道包含它的类。至于循环引用问题,几乎不可能在没有看到您的代码的情况下提出解决方案。将共享类型提取到单独的单元中是一种常见的方法,但如果没有代码可以更具体地使用它。
  • @J... 感谢您的建议。你能举个例子,这样的回调和方法注入是怎样的?
  • 这是我从记忆中挖掘出来的最接近的——上下文有点不同,它在线程上下文中工作,但如果它对你来说是一个新概念,那么实现回调的一般想法就在那里:stackoverflow.com/a/19298903/327083

标签: delphi circular-reference


【解决方案1】:

前向声明不能跨单元工作。当一个单元前向声明一个记录/类时,同一个单元也必须定义该记录/类。

我建议定义一个TMesh 实现的IMesh 接口,然后让TVerticesTTriangleFaces 直接使用IMesh 而不是TMesh。这样,就没有循环引用,并且接口可以为需要的任何字段值公开属性。并且TComponent 禁用了已​​实现接口的引用计数,因此内存泄漏不是问题。

MeshIntf.pas:

unit MeshIntf;

interface

type
  IMesh = interface(IInterface)
    ['{30315BC6-9A2E-4430-96BB-297D11C9DB5D}']
    // methods for performing common tasks...
    // properties for reading/setting needed values...
  end;

implementation

end.

顶点.pas:

unit Vertices;

interface

uses
  System.Types, System.Generics.Collections, MeshIntf;

type
  TVertex = record
    Point: TPoint3D;
    //other fields
  end;

  TVertices = class(TList<TVertex>)
  public
    Mesh: IMesh;
    constructor Create(const AMesh: IMesh); reintroduce;
    procedure DoSomethingWithAllVertices;
    //other methods
  end;

implementation

constructor TVertices.Create(const AMesh: IMesh);
begin
  inherited Create;
  Mesh := AMesh;
end;

procedure TVertices.DoSomethingWithAllVertices;
begin
  // use properties/methods of Mesh as needed...
end;

end.

TriangleFaces.pas:

unit TriangleFaces;

interface

uses
  System.Generics.Collections, MeshIntf;

type
  TTriangleFace = record
    Vertices: Array[0..2] of Integer;
    //other fields
  end;

  TTriangleFaces = class(TList<TTriangleFace>)
  public
    Mesh: IMesh;
    constructor Create(const AMesh: IMesh); reintroduce;
    procedure DoSomethingWithAllTriangleFaces;
    //other methods
  end;

implementation

constructor TTriangleFaces.Create(const AMesh: IMesh);
begin
  inherited Create;
  Mesh := AMesh;
end;

procedure TTriangleFaces.DoSomethingWithAllTriangleFaces;
begin
  // use properties/methods of Mesh as needed...
end;

end.

Mesh.pas:

unit Mesh;

interface

uses
  Classes, MeshIntf, Vertices, TriangleFaces;

type
  TMesh = class(TComponent, IMesh)
  public
    Vertices: TVertices;
    TriangleFaces: TTriangleFaces;
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    //other fields & methods, and IMesh implementation
  end;

implementation

constructor TMesh.Create(AOwner: TComponent);
begin
  inherited;
  Vertices := TVertices.Create(Self as IMesh);
  TriangleFaces := TTriangleFaces.Create(Self as IMesh);
end;

destructor TMesh.Destroy;
begin
  Vertices.Free;
  TriangleFaces.Free;
  inherited;
end;

end.

如果您不需要在设计时将TMesh 提供给表单设计器和对象检查器,您应该从TInterfacedObject 而不是TComponent 派生它。但是你需要做一些小的调整来正确处理引用计数(TComponent 禁用)。特别是,TVerticesTTriangleFaces 需要使用弱引用,以免增加 TMesh 的引用计数并导致内存泄漏(因为它的引用计数将永远下降到 0在那种情况下):

MeshIntf.pas:

unit MeshIntf;

interface

uses
  System.Types;

type
  TVertex = record
    Point: TPoint3D;
    //other fields
  end;

  IVertices = interface(IInterface)
    ['{97A70A11-C8B6-4DBC-807B-B9E0C6953B9E}']
    // methods for performing tasks...
    procedure DoSomethingWithAllVertices;
    function GetVertex(Index: Integer): TVertex;
    // properties for reading/setting values...
    property Vertex[Index: Integer]: TVertex read GetVertex;
  end;

  TTriangleFace = record
    Vertices: Array[0..2] of Integer;
    //other fields
  end;

  ITriangleFaces = interface(IInterface)
    ['{A1ED479B-7430-4524-A630-FDDE212375BB}']
    // methods for performing tasks...
    procedure DoSomethingWithAllTriangleFaces;
    function GetFace(Index: Integer): TTriangleFace;
    // properties for reading/setting values...
    property Face[Index: Integer]: TTriangleFace read GetFace;
  end;

  IMesh = interface(IInterface)
    ['{30315BC6-9A2E-4430-96BB-297D11C9DB5D}']
    // methods for performing common tasks...
    function GetVertices: IVertices;
    function GetTriangleFaces: ITriangleFaces;
    // properties for reading/setting values...
    property Vertices: IVertices read GetVertices;
    property TriangleFaces: ITriangleFaces read GetTriangleFaces;
  end;

implementation

end.

顶点.pas:

unit Vertices;

interface

uses
  System.Generics.Collections, MeshIntf;

type
  TVertices = class(TInterfacedObject, IVertices)
  private
    // Delphi 10.1 Berlin adds [weak] support to all compilers,
    // it was previously only available on the mobile compilers...
    {$IFDEF WEAKINTFREF}
    [weak] fMesh: IMesh;
    {$ELSE}
    fMesh: Pointer;
    {$ENDIF}

    fVertices: TList<TVertex>;

  public
    constructor Create(AMesh: IMesh);
    destructor Destroy; override;

    //other methods

    // IVertices implementation
    procedure DoSomethingWithAllVertices;
    function GetVertex(Index: Integer): TVertex;
  end;

implementation

constructor TVertices.Create(AMesh: IMesh);
begin
  inherited Create;
  fMesh := {$IFDEF WEAKINTFREF}AMesh{$ELSE}Pointer(AMesh){$ENDIF};
  fVertices := TList<TVertex>.Create;
end;

destructor TVertices.Destroy;
begin
  fVertices.Free;
  inherited;
end;

procedure TVertices.DoSomethingWithAllVertices;
begin
  // use properties of fMesh as needed...

  // if WEAKINTFREF is not defined simply type-cast the Mesh
  // pointer as IMesh(fMesh) when accessing its members...
end;

function TVertices.GetVertex(Index: Integer): TVertex;
begin
  Result := fVertices[Index];
end;

end.

TriangleFaces.pas:

unit TriangleFaces;

interface

uses
  System.Generics.Collections, MeshIntf;

type
  TTriangleFaces = class(TInterfacedObject, ITriangleFaces)
  private
    // Delphi 10.1 Berlin adds [weak] support to all compilers,
    // it was previously only available on the mobile compilers...
    {$IFDEF WEAKINTFREF}
    [weak] fMesh: IMesh;
    {$ELSE}
    fMesh: Pointer;
    {$ENDIF}

    fFaces: TList<TTriangleFace>;

  public
    constructor Create(AMesh: IMesh);
    destructor Destroy; override;

    //other methods

    // ITriangleFaces implementation
    procedure DoSomethingWithAllTriangleFaces;
    function GetFace(Index: Integer): TTriangleFace;
  end;

implementation

constructor TTriangleFaces.Create(AMesh: IMesh);
begin
  inherited Create;
  fMesh := {$IFDEF WEAKINTFREF}AMesh{$ELSE}Pointer(AMesh){$ENDIF};
  fFaces := TList<TTriangleFace>.Create;
end;

destructor TTriangleFaces.Destroy;
begin
  fFaces.Free;
  inherited;
end;

procedure TTriangleFaces.DoSomethingWithAllTriangleFaces;
begin
  // use properties of fMesh as needed...

  // if WEAKINTFREF is not defined simply type-cast the Mesh
  // pointer as IMesh(fMesh) when accessing its members...
end;

function TTriangleFaces.GetFace(Index: Integer): TTriangleFace;
begin
  Result := fFaces[Index];
end;

end.

Mesh.pas:

unit Mesh;

interface

uses
  MeshIntf;

type
  TMesh = class(TInterfacedObject, IMesh)
  private
    // note, these are *strong* references, not*weak* references!
    fVertices: IVertices;
    fTriangleFaces: ITriangleFaces;

  public
    constructor Create;

    //other fields & methods

    // IMesh implementation
    function GetVertices: IVertices;
    function GetTriangleFaces: ITriangleFaces;
  end;

implementation

uses
  Vertices, TriangleFaces;

constructor TMesh.Create;
begin
  inherited;
  fVertices := TVertices.Create(Self as IMesh);
  fTriangleFaces := TTriangleFaces.Create(Self as IMesh);
end;

function TMesh.GetVertices: IVertices;
begin
  Result := fVertices;
end;

function TMesh.GetTriangleFaces: ITriangleFaces;
begin
  Result := fTriangleFaces;
end;

end.

在创建 TMesh 对象时,请确保您的代码中某处有一个非弱的 IMesh 变量,以便它保持活动状态,直到您不再需要它为止:

var
  Meth: IMesh; // or a class member or a global, wherever you need it

Mesh := TMesh.Create;
...
Mesh := nil;

(正确的)引用计数会为您处理剩下的事情。

【讨论】:

  • TMesh 继承自 TComponentTComponent 已禁用引用计数。现在你有一个接口IMesh,它不会自行清理。
  • 这很好。当TMesh 被释放时,TVertices/TTriangleFaces 将被释放。 TMesh 的生命周期不依赖于 TVertices/TTriangleFaces 的生命周期,反之亦然。 TMeshOwner 将在 Owner 被释放时自动释放它。如果Owner 为nil,则创建TMesh 的代码负责手动释放它。这里没有泄漏问题。仅当释放所有 IMesh 实例时需要自动释放 TMesh 时,引用计数才重要,而这里不是这种情况。
  • @user3384674:如果你在没有TComponent的情况下走interface路线,那么可以,除非你自己实现QueryInterface()_AddRef()_Release()方法(TInterfacedObject实现它们为你)。如果TMesh被引用计数,那么TVertices/TTriangleFaces需要使用弱引用来避免增加引用计数导致TMesh被泄露(循环强引用 i> 问题)。创建TMesh 对象的人将需要一个IMesh 变量来使其保持活动状态,直到不再需要为止。
  • @user3384674:不明显,不(只是虚拟方法调用的开销)。但是,如果您允许直接访问TVertices/TTriangleFaces 对象IMesh,您将创建新的单元循环引用(MeshIntf 将需要引用Vertices/TriangleFaces,参考MeshIntf - 现在你又回到了第一格!)。 MeshIntf 可以定义额外的接口供TVertices/TTriangleFaces 实现,然后IMesh 可以根据需要使用这些接口。使用这样的接口可以将单元彼此分离。
  • 是的,它会更慢。内存布局不太适合缓存。
【解决方案2】:

您当前的解决方案已经是解决问题的最佳方法。将这些类型拆分为单独的单元会产生重大障碍,并会导致代码笨拙。

虽然我可以理解您将这些类型分开的愿望,但您必须在这种愿望与生成的代码的清晰性之间取得平衡。在这种情况下,分叉的负面后果会影响积极因素。保持代码不变。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-09-05
    • 1970-01-01
    • 2010-11-20
    • 2011-06-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多