问题
I have a triangular mesh structure in Delphi 10.
For performance reasons I store the data of the mesh's vertices, triangle faces, etc. in descendants of TList.
I let the TLists do the calculations for every member of the list. For these calculations I need access to some fields of the TMesh structure. Therefore during the creation of TMesh and subsequently the creation of the lists I assign the parent TMesh to the lists. I use a forward declaration of TMesh to do so. Please see the following code:
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;
This works fine.
However since my project is growing I am getting more and more code and I want to distribute the list classes in separate units. This results in the problem of circular unit references.
The problem of circular unit references seems quite well known. I checked for possible solutions but I cannot find one which seem to fit my problem. Some say that if you run into circular unit references the code is poorly designed.
How can I improve the design and at the same time keep the calculation performance high?
What are other possibilities to solve the problem?
Thank you very much!
回答1:
Your current solution is already the best way to solve the problem. Splitting these types into separate units creates significant obstacles and will lead to unwieldy code.
Whilst I can understand your desire to split these types apart, you have to balance that desire against the clarity of the resulting code. In this case the negative consequences of splitting far out weigh the positives. Leave the code as it is.
回答2:
Forward declarations do not work across units. When a unit forward declares a record/class, the same unit must also define the record/class.
I would suggest defining an IMesh
interface that TMesh
implements, and then have TVertices
and TTriangleFaces
use IMesh
instead of TMesh
directly. That way, there is no circular reference, and the interface can expose properties for whatever field values are needed. And TComponent
disables reference counting for implemented interfaces, so memory leaking is not an issue.
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.
If you don't need TMesh
to be available at design-time to the Form Designer and Object Inspector, you should derive it from TInterfacedObject
instead of TComponent
. But then you need to make some small tweaks to handle reference counting correctly (which TComponent
disables). In particular, TVertices
and TTriangleFaces
need to use weak referencing so as not to increment the TMesh
's reference count and cause a memory leak (since its reference count will ever fall to 0 in that scenario):
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.
Just be sure you have a non-weak IMesh
variable somewhere in your code when creating the TMesh
object so it stays alive until you do not need it anymore:
var
Meth: IMesh; // or a class member or a global, wherever you need it
Mesh := TMesh.Create;
...
Mesh := nil;
(Proper) reference counting will take care of the rest for you.
来源:https://stackoverflow.com/questions/37174575/delphi-better-design-to-avoid-circular-unit-reference