【问题标题】:Why does Delphi call incorrect constructor during dynamic object creation?为什么Delphi在动态对象创建过程中调用了错误的构造函数?
【发布时间】:2017-05-24 17:30:12
【问题描述】:

我在动态对象创建期间调用了不正确的虚拟 Create() 方法时遇到问题。调用父方法而不是子方法。

我已经查看了这些帖子,但无法弄清楚:
Delphi - Create class from a string

Exploring TRTTIType and Descendants

Can I pass a Class type as a procedure parameter

这里是Class_References

我有以下课程:

TCellObj = class(TPhysicsObj)
  ...
  public
    constructor Create(RgnMgr : TObject); virtual; //RgnMgr should be TRgnManager
    destructor Destroy;
    ...
  end;

  TCellObjClass = Class of TCellObj;

--------------------------------

 TCellTrialAObj = class(TCellObj)
    ...
    public
      ...
      constructor Create(RgnMgr : TObject); virtual; //RgnMgr should be TRgnManager
  end;
--------------------------------

TRgnManager = class (TObject)
  ... 
  public
    function NewCell(ClassRef : TCellObjClass) : TCellObj; 
    ...
  end;

  ....

  function TRgnManager.NewCell(ClassRef : TCellObjClass) : TCellObj;
  var CellObj : TCellObj;

  begin
    CellObj := ClassRef.Create(Self);
    CellObj.DefaultInitialize;
    CellObj.Color := TAlphaColors.Slategray;
    FCellsList.Add(CellObj);  //This will own objects.
    SetSelection(CellObj);
    Result := CellObj;
  end;

最后我通过下面这行开始动态对象的创建过程:

RgnManager.NewCell(TCellTrialAObj);

我的目标是让 TRgnManager.NewCell 基于作为参数传入的派生类创建 TCellObj 的任何后代。在使用过程中,我会将结果类型转换为适当的类类型。

当我在 NewCell 中使用调试器单步执行代码时,Evaluate/Modify 工具告诉我 ClassRef = TCellTrialAObj 符合预期。

但是当我进入 ClassRef.Create(self) 行时,它会转到 TCellObj.Create(),而不是像我预期的那样转到 TCellTrialAObj.Create()。这是我不明白的部分。

将结果分配给 CellObj 后,评估/修改工具告诉我 CellObj.ClassName = 'TCellTrialAObj';

所以ClassRef是TCellTrialAObj的,那为什么Create()函数没有调用TCellTrialAObj.Create()??

提前致谢。

附:我正在使用 Embarcadero® Delphi 10 西雅图版本 23.0.22248.5795

附录

我使用上面链接中的示例在下面拼凑了这个函数。它似乎工作,并根据需要调用 TCellTrialAObj.Create 。但我不明白我到底是如何、为什么或者我是否真的做对了。谁能解释一下?

  function TRgnManager.NewCell(ClassRef : TCellObjClass) : TCellObj;
  var CellObj : TCellObj;
      RT : TRttiType;
      C : TRttiContext;
      T : TRttiInstanceType;
      V : TValue;

  begin
    C := TRttiContext.Create;
    T := (C.GetType(ClassRef) as TRttiInstanceType);
    V := T.GetMethod('Create').Invoke(T.metaClassType,[self]);
    C.Free;
    CellObj := V.AsObject as TCellObj;

    //CellObj := ClassRef.Create(Self);
    CellObj.DefaultInitialize;
    CellObj.Color := TAlphaColors.Slategray;
    FCellsList.Add(CellObj);  //This will own objects.
    SetSelection(CellObj);
    Result := CellObj;
  end;

【问题讨论】:

  • 您是否沉浸在警告和提示中,或者您为什么错过编译器告诉您出了什么问题?
  • 析构函数也应该覆盖它。您可以在 TRgnManager 上使用前向声明,以便将其用作参数。代码中的问题比您发现的单个问题多得多。
  • @StefanGlienke 和 DavidHeffernan 感谢您的反馈。在我尝试自我教育并避免将来出现类似问题时,请查看其他 cmets。

标签: delphi dynamic


【解决方案1】:

编译器警告将在此处为您提供帮助。编译时您可能会注意到收到警告Method 'Create' hides virtual method of base type 'TCellObj'。这是因为您已将后代TCellTrialAObj 的构造函数声明为virtual,而我们却推断您希望它为override

这里有一个小例子演示了你想要的功能。

program Project1;

{$APPTYPE CONSOLE}

type
  TCellObj = class
    public
      constructor Create; virtual;
  end;

  TCellObjClass = Class of TCellObj;

  TCellTrialAObj = class(TCellObj)
    public
      constructor Create; override;
  end;

constructor TCellObj.Create;
begin
  WriteLn('TCellObj');
end;

constructor TCellTrialAObj.Create;
begin
  WriteLn('Calling base constructor...');
  inherited;
  WriteLn('...and now in TCellTrialAObj constructor');
end;

function NewCell(ClassRef : TCellObjClass) : TCellObj;
var
  CellObj : TCellObj;
begin
  CellObj := ClassRef.Create;;
  Result := CellObj;
end;

var
  LCellObj : TCellObj;
begin
  LCellObj := NewCell(TCellTrialAObj);
  ReadLn;
end.

顺便说一句,您在这里使用注释来建议类型限制:

 constructor Create(RgnMgr : TObject); virtual; //RgnMgr should be TRgnManager

但是,可以对 TRgnMgr 类进行前向声明并在以后完全定义它,从而允许您包含更健壮的正式类型限制。

  TRgnMgr = class;  { Declare type... }

  TCellObj = class
    public
      constructor Create(RgnMgr : TRgnMgr); virtual;
  end;

  TCellObjClass = Class of TCellObj;

  TCellTrialAObj = class(TCellObj)
    public
      constructor Create(RgnMgr : TRgnMgr); override;
  end;

  TRgnMgr = class  { but define it later }
    private
      FFoo : integer;
  end;

【讨论】:

  • @kdtop 请查看我对类型限制的编辑 - 我认为这也会很有趣。
  • @kdtop 除了理解多态之外,我希望你能吸取教训:“永远不要忽略编译器警告。”他们告诉你的事情至少是可疑的,即使不是完全错误。您可以始终修改您的代码,使其不发出警告。所以你应该理解并消除你看到的任何编译器警告。
  • @kdtop 你是对的,但我可能会争辩说,如果这些类直接相互依赖,那么它们可能属于在同一个单元中。
  • @kdtop 根据您对 Craig 的问题,我认为这可能是一个新的、单独的问题的主题。请参阅:meta.stackoverflow.com/q/266767/327083
  • @kdtop 正如 J 所说,通常应该提出一个新问题。但是在这种情况下,如果你搜索,你会发现这个和相关的问题之前已经在这里提出过。在 google 中尝试以下操作:site:stackoverflow.com delphi "hides virtual method of base" 我只是预先警告您在搜索中会遇到 reintroduce 关键字。这是 Delphi 的一个所谓的“功能”,它抑制了有关在层次结构中破坏多态性的警告。注意:它没有修复任何东西,你最好重新考虑你的 OO 设计。您不需要隐藏任何虚拟方法。
猜你喜欢
  • 2020-11-01
  • 2011-06-29
  • 2020-07-03
  • 1970-01-01
  • 2020-07-05
  • 2021-06-30
  • 2016-08-11
  • 2013-10-29
  • 2019-07-25
相关资源
最近更新 更多