2016-05-11 9 views
3

私はDelphi 10で三角形のメッシュ構造を持っています。デルファイ:円形のユニット参照を避けるためのデザインが改善されましたか?

パフォーマンスの理由から、メッシュの頂点、三角形の面などのデータを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; 

これは問題なく動作します。

私のプロジェクトが成長して以来、ますますコードが増えています。リストクラスを別々のユニットに分けて配布したいと思います。これは、循環ユニット参照の問題をもたらす。

循環ユニット参照の問題は、かなりよく知られているようです。私は可能な解決策を確認したが、私の問題に見合うものは見つけられなかった。あなたが円形の単位の参照に遭遇すると、そのコードはあまり設計されていないと言う人もいます。

デザインを改善すると同時に、計算​​パフォーマンスを高く保つにはどうすればよいですか?

問題を解決する他の方法はありますか?

ありがとうございました!

+0

メッシュに直接アクセスするのではなく、 'TVertices'がコールバックを実行するのはなぜですか?そうすれば 'TMesh'がメソッドを挿入することができ、それを含むクラスについて知る必要がある' TVertices'を取得できます。循環参照問題に関しては、コードを見ずに解決策を提案することはほとんど不可能です。共用型を分離単位に抽出することはこれを行うための一般的な方法ですが、コードを使用しなければ、より具体的にするのは難しくありません。 –

+0

@J ...提案に感謝します。そのようなコールバックとメソッドの注入がどのように見えるかの例を挙げてください。 – user3384674

+0

これは私がメモリから掘り下げたものに近いです - コンテキストは少し異なり、スレッドのコンテキストで動作しますが、コールバックを実装する一般的な考え方は、あなたにとって斬新なコンセプトの場合にあります:http:// stackoverflow。 com/a/19298903/327083 –

答えて

7

あなたの現在のソリューションは、すでに問題を解決するための最良の方法です。これらのタイプを別々のユニットに分割することは、大きな障害となり、扱いにくいコードにつながります。

これらのタイプを分割したいという希望は理解できますが、結果のコードの明瞭さとその欲求とのバランスを取る必要があります。この場合、遠くに分裂するという否定的な結果は、ポジティブの重さを左右する。コードをそのまま残してください。

9

前方宣言はユニット間では機能しません。ユニットフォワードがレコード/クラスを宣言するとき、同じユニットはレコード/クラスも定義しなければなりません。

私はその後TMesh実装、および直接の代わりにTMeshTVerticesTTriangleFaces使用IMeshを持っていることをIMeshインタフェースを定義することをお勧め。そうすれば、循環参照はなく、インタフェースは必要なフィールド値のプロパティを公開することができます。 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をデザイン時にフォームデザイナーとオブジェクトインスペクタに使用する必要がない場合は、TComponentの代わりにTInterfacedObjectから派生させる必要があります。しかし、参照カウントを正しく処理するには、小さな調整を行う必要があります(TComponentは無効です)。特に、TVerticesTTriangleFacesTMeshの参照カウントをインクリメントして(その参照カウントが今までそのシナリオに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; 

(適切な)参照カウントがあなたのために残ります。

+0

'TMesh'は' TComponent'と 'TComponent'から継承し、参照カウントを無効にしました。今すぐあなた自身の後にクリーンアップされないインターフェイス 'IMesh'を持っています。 – Johan

+1

これは完璧です。 'TMesh'が解放されると' TVertices'/'TTriangleFaces'が解放されます。 'TMesh'の存続期間は' TVertices'/'TTriangleFaces'の存続期間に依存しません。それは逆です。 'TMesh'の' Owner'は 'Owner'が解放されると自動的に解放されます。 'Owner'がnilの場合、' TMesh'を生成するコードは手動で解放します。ここには漏れの問題はありません。参照カウントは、すべての 'IMesh'インスタンスが解放されたときに' TMesh'を自動的に解放する必要がある場合にのみ重要です。ここではそうではありません。 –

+0

'TComponent'自体はいくつかのインターフェースを実装しており、コンポーネント作成者がコンポーネントにカスタムインターフェースを実装することは珍しいことではありません。それはうまく動作します。 –

関連する問題