Delphi: 更好的设计来避免循环单元引用?
Delphi: better design to avoid circular unit reference?
我在Delphi10.
中有一个三角网状结构
出于性能原因,我将网格的顶点、三角形面等数据存储在 TList 的后代中。
我让 TLists 为列表的每个成员进行计算。对于这些计算,我需要访问 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;
这很好用。
但是,由于我的项目不断发展,我得到的代码越来越多,我想将列表 类 分发到不同的单元中。这导致循环单元引用的问题。
循环单元引用的问题似乎是众所周知的。我检查了可能的解决方案,但找不到似乎适合我的问题的解决方案。有人说,如果您 运行 进入循环单元引用,则代码设计不佳。
如何在改进设计的同时保持较高的计算性能?
解决问题的其他可能性是什么?
非常感谢!
前向声明不适用于跨单位。当一个单位转发声明 record/class 时,同一单位还必须定义 record/class.
我建议定义一个 TMesh
实现的 IMesh
接口,然后让 TVertices
和 TTriangleFaces
使用 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.
Vertices.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
禁用)。特别是 TVertices
和 TTriangleFaces
需要使用 弱引用 以免增加 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.
Vertices.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;
(适当的)引用计数将为您处理剩下的事情。
您目前的方案已经是解决问题的最佳方式。将这些类型拆分为单独的单元会造成重大障碍,并会导致代码笨拙。
虽然我能理解您将这些类型分开的愿望,但您必须在这种愿望与生成代码的清晰度之间取得平衡。在这种情况下,分裂的负面后果远远超过积极因素。保持代码不变。
我在Delphi10.
中有一个三角网状结构出于性能原因,我将网格的顶点、三角形面等数据存储在 TList 的后代中。
我让 TLists 为列表的每个成员进行计算。对于这些计算,我需要访问 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;
这很好用。
但是,由于我的项目不断发展,我得到的代码越来越多,我想将列表 类 分发到不同的单元中。这导致循环单元引用的问题。
循环单元引用的问题似乎是众所周知的。我检查了可能的解决方案,但找不到似乎适合我的问题的解决方案。有人说,如果您 运行 进入循环单元引用,则代码设计不佳。
如何在改进设计的同时保持较高的计算性能?
解决问题的其他可能性是什么?
非常感谢!
前向声明不适用于跨单位。当一个单位转发声明 record/class 时,同一单位还必须定义 record/class.
我建议定义一个 TMesh
实现的 IMesh
接口,然后让 TVertices
和 TTriangleFaces
使用 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.
Vertices.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
禁用)。特别是 TVertices
和 TTriangleFaces
需要使用 弱引用 以免增加 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.
Vertices.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;
(适当的)引用计数将为您处理剩下的事情。
您目前的方案已经是解决问题的最佳方式。将这些类型拆分为单独的单元会造成重大障碍,并会导致代码笨拙。
虽然我能理解您将这些类型分开的愿望,但您必须在这种愿望与生成代码的清晰度之间取得平衡。在这种情况下,分裂的负面后果远远超过积极因素。保持代码不变。