【问题标题】:Referencing a class inside another class by address通过地址引用另一个类中的一个类
【发布时间】:2010-12-25 13:16:57
【问题描述】:

我有以下问题:我有一个实体类“TEntity”和一个网格类“TMesh”,TEntity 需要知道它的元素(TMesh)何时被删除。有没有一种可能的工作方式我可以从 TMesh 析构函数中调用 TEntity 方法“OnMeshRemove”?

//uTEntity

interface

uses
  uTMesh;

type
  TEntity = class
    Mesh : TMesh;
    constructor Create(); overload;
    procedure OnMeshRemove();
  end;

implementation

constructor TEntity.Create();
begin
  Mesh := TMesh.Create();
  Mesh.EntityContainer := @self;
end;

procedure TEntity.OnMeshRemove();
begin
  //Do stuff
end;

end.

//uTMesh

interface

type
  TMesh = class
    EntityContainer : Pointer;
    destructor Remove();
  end;

implementation

uses
  uTEntity;

destructor TMesh.Remove();
var
  PEntity : ^TEntity;  
begin
  PEntity := EntityContainer;
  if Assigned( PEntity^ ) then
  begin
    PEntity^.OnMeshRemove();
  end;

  inherited Destroy();
end;

例子:

var
  Ent : TEntity;
begin

  Ent := TEntity.Create();
  Ent.Mesh.Remove();

  //I want Ent.OnMeshRemove to be called. In my example code, there is a pointer problem. I need to solve that. Thanks!

end;

PS:我不想拥有像 TEntity.RemoveMesh(); 这样的 TEntity 程序;

【问题讨论】:

  • 很难知道您在此处尝试什么,但在 Delphi 中,所有析构函数都称为 Destroy,它们从不直接调用 - 您改为调用 Free。
  • 如果这是您的要求,您可以将指针类型转换为实际类型 TEntity(EntityContainer).OnMeshRemove();..
  • @Sertac Akyuz 仅在 EntityContainer := @self 更改为 EntityContainer := Pointer(self) 时才有效
  • 为什么,调用 virtual Destroy 而不是 static Free 是完全合法的
  • @user205376 在您调用 free 的析构函数中,因为该对象可能尚未创建并且未分配自我的免费测试

标签: delphi class pointers methods


【解决方案1】:

Delphi 中的所有对象都是指针类型,所以不需要尊重它。这里稍微简单一点

type
  TEntity = class
  public
    Mesh: TMesh;
    constructor Create;
    destructor Destroy; override;
  end;

implementation

constructor TEntity.Create;
begin
  inherited Create;
  Mesh := TMesh.Create;
  Mesh.EntityContainer := Self;
end;

procedure TEntity.Destroy;
begin
  if Mesh <> nil then
  begin
    Mesh.EntityContainer := nil;
    FreeAndNil(Mesh); 
  end;
  inherited Destroy;
end;

//***************************************************

type
  TMesh = class
    EntityContainer: TObject;
    destructor Destroy; override;
  end;

implementation

uses
  uTEntity;

destructor TMesh.Destroy;
begin
  if (EntityContainer <> nil) and (TEntity(EntityContainer).Mesh = Self) then
    TEntity(EntityContainer).Mesh := nil;
  EntityContainer := nil;
  inherited Destroy;
end;

【讨论】:

    【解决方案2】:

    你的 TEntity 实例应该向 TMesh 实例注册自己,这样当 TMesh 实例被释放时,它会修改关于它的 TEntity 实例。

    如果你的类是从 TComponent 类派生的,那么这个机制已经为你实现了;每个 TComponent 实例都有一个名为 FreeNotification 的方法和一个名为 Notification 的方法。任何 TComponent 实例都可以通过调用其 FreeNotification 方法并将自己作为参数传递给其他组件来注册自己。每当一个组件被销毁时,它会检查注册的组件列表以获得它的空闲通知,并调用每个注册组件的 Notification 方法。这样,每当其他组件即将被销毁时,都会通知注册组件。

    如果一个 TComponent 实例是另一个 TComponent 实例的所有者(在您的情况下,TEntity 可以是 TMesh 实例的所有者),那么只要 TMesh 实例被删除,它就会自动收到通知。您需要做的就是重写它的 Notification 方法并在该方法中做任何您想做的事情。

    如果您不想从 TComponent 类派生您的类或出于任何原因不想使用 Delphi 的实现,您可以在您自己的类中实现相同的行为。您需要一个 TMesh 类中的内部列表,其中包含应通知的类列表。你还需要一个方法来向你的 TMesh 类注册一个类,最终你需要一个 TEntity 类中的方法,当 TMesh 被释放时应该调用它。

    这里是一个简单的源代码,仅用于演示一般想法。请注意,此示例代码不是线程安全的,并且可能缺少一些其他检查。我只是快速写了它,向您展示如何实现这样的想法:

    unit Unit1;
    
    interface
    
    uses
      Classes, Generics.Collections;
    
    type
      TBaseClass = class
      private
        FNotificationList : TList<TBaseClass>;
      protected
        procedure Notify(AClass: TBaseClass); virtual;
      public
        procedure RegisterForNotification(AClass: TBaseClass);
        procedure UnregisterNotification(AClass: TBaseClass);
        constructor Create;
        destructor Destroy; override;
      end;
    
      TMesh = class(TBaseClass)
    
      end;
    
      TEntity = class(TBaseClass)
      private
        FMesh : TMesh;
        FOnMeshRemoved : TNotifyEvent;
        procedure SetMesh(Value: TMesh);
      protected
        procedure Notify(AClass: TBaseClass); override;
        procedure DoMeshRemoved; virtual;
      public
        constructor Create;
        destructor Destroy; override;
        property Mesh : TMesh read FMesh write SetMesh;
        property OnMeshRemoved : TNotifyEvent read FOnMeshRemoved write FOnMeshRemoved;
      end;
    
    
    implementation
    
    { TBaseClass }
    
    constructor TBaseClass.Create;
    begin
      inherited;
      FNotificationList := TList<TBaseClass>.Create;
    end;
    
    destructor TBaseClass.Destroy;
    var
      AClass: TBaseClass;
    begin
      if Assigned(FNotificationList) then
      begin
        if (FNotificationList.Count > 0) then
          for AClass in FNotificationList do
            AClass.Notify(Self);
    
        FNotificationList.Free;
        FNotificationList := nil;
      end;
    
      inherited;
    end;
    
    procedure TBaseClass.Notify(AClass: TBaseClass);
    begin
    end;
    
    procedure TBaseClass.RegisterForNotification(AClass: TBaseClass);
    begin
      if not Assigned(AClass) then
        Exit;
      if FNotificationList.IndexOf(AClass) < 0 then
        FNotificationList.Add(AClass);
    end;
    
    procedure TBaseClass.UnregisterNotification(AClass: TBaseClass);
    begin
      if not Assigned(AClass) then
        Exit;
      if FNotificationList.IndexOf(AClass) >= 0 then
        FNotificationList.Remove(AClass);
    end;
    
    { TEntity }
    
    constructor TEntity.Create;
    begin
      inherited;
    
    end;
    
    destructor TEntity.Destroy;
    begin
      if Assigned(FMesh) then
        FMesh.UnregisterNotification(Self);
    
      inherited;
    end;
    
    procedure TEntity.DoMeshRemoved;
    begin
      if Assigned(FOnMeshRemoved) then
        FOnMeshRemoved(Self);
    end;
    
    procedure TEntity.Notify(AClass: TBaseClass);
    begin
      inherited;
      FMesh := nil;
      DoMeshRemoved;
    end;
    
    procedure TEntity.SetMesh(Value: TMesh);
    begin
      if Assigned(FMesh) then
      begin
        FMesh.UnregisterNotification(Self);
        FMesh := nil;
      end;
    
      if Assigned(Value) then
      begin
        FMesh := Value;
        FMesh.RegisterForNotification(Self);
      end;
    end;
    
    end.
    

    在这段代码中,TEntity 和 TMesh 都是从提供通知机制的 TBaseClass 派生的。 TEntity 最初不会创建任何 TMesh 实例,但您可以将创建的 TMesh 实例分配给它的 Mesh 属性。这样做将使 TEntity 将该值分配给其 FMesh 字段,并调用其 RegisterForNotification 类,以便在网格被破坏时通知它。

    当网格被销毁时,它会遍历所有向它注册的对象,并调用它们的 Notify 方法。这里是 TEntity 类的 Notify 方法。在 TEntity 的 Notify 方法中,它首先将其 FMesh 字段设为 nil,因为该对象正在被销毁,不需要保留对已死对象的引用。然后它调用 DoMeshRemove 方法,该方法是 OnMeshRemove 事件的事件调用者。

    【讨论】:

      【解决方案3】:

      编辑:回到电脑后面。

      维护某个类的预定对象列表的经典方法是使用TCollection/TCollectionItem

      TCollection/TCollectionItem 在 Delphi 中被大量使用(参见this list)。 它们的重量比TComponent 轻(自动维护Owner/Components/ComponentCount 并具有FreeNotification),因为TCollectionItem 和TCollection 都从the TPersistent branch 下降而不是TComponent branch
      TCollection 有一个很好的虚拟Notify 方法:

      procedure TCollection.Notify(Item: TCollectionItem; Action: TCollectionNotification);
      begin
        case Action of
          cnAdded: Added(Item);
          cnDeleting: Deleting(Item);
        end;
      end;
      

      从 Delphi 2009 开始,您有了泛型,因此使用 TList 可以带来很大的回报(在您的演员阵容中 TList&lt;TEntity&gt;,因为它包含这个非常好的 Notify 方法和 OnNotify 事件:

      procedure TList<T>.Notify(const Item: T; Action: TCollectionNotification);
      begin
        if Assigned(FOnNotify) then
          FOnNotify(Self, Item, Action);
      end;
      

      如果您的TMesh 确实是TEntity 的集合/列表,这两种解决方案效果很好。
      如果TMeshTEntity 的非列表图,那么最好从TComponent 下降,就像his answer 中解释的vcldeveloperlink text

      Andy Bulka 有一篇关于lists and collections 的各种使用方式的精彩博文,其中包括TCollection/TCollectionItem usage 的平衡视图。

      --杰罗恩

      旧答案(修复了很棒的 iPad 自动完成错误):

      抱歉,回答简短,因为我在路上只携带移动设备。

      看起来您的网格是实体的容器。
      如果是这样,那么您应该查看 TCollection 和 TCollectionItem。
      从前者派生网格,从后者派生实体。

      delphi vcl 源代码包含许多这样的示例:字段/字段或操作/操作是很好的入门。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-07-26
        • 1970-01-01
        • 2015-05-13
        • 1970-01-01
        • 1970-01-01
        • 2022-10-25
        相关资源
        最近更新 更多