如何使 Delphi 在动态创建时调用正确的构造函数?

smtd7mpg  于 2023-02-08  发布在  其他
关注(0)|答案(3)|浏览(191)

我的Delphi2006在动态创建过程中似乎调用了不正确的构造函数。
5年前我问过几乎完全相同的问题(Why does Delphi call incorrect constructor during dynamic object creation?),我已经检查过了。但是那个线程有覆盖虚拟调用的问题,我现在没有了。我也试着通过StackOverflow搜索匹配的问题,但是找不到答案。
我正在处理遗留代码,所以我没有写太多。(如果你看到下面的注解中有"//kt "添加了一些东西,那就是我)。
代码有基类TPCEItem,如下所示。注意它没有构造函数。

TPCEItem = class(TObject)
  {base class for PCE items}
  private
    <irrelevent stuff>
  public
    <irrelevent stuff>
  end;

接下来,是用于传递参数的类类型(更多信息见下文)。

TPCEItemClass = class of TPCEItem;

接下来我有一个子类,如下所示。注意它确实有一个构造函数。编译器不允许我在这个create方法中添加"override",因为声明它的祖先类(TObject)没有将它定义为virtual。

TPCEProc = class(TPCEItem)
  {class for procedures}
  protected
    <irrelevent stuff>
  public
    <irrelevent stuff>
    constructor Create;
    destructor Destroy; override;
  end;

然后代码有一个复制数据的函数,它是后代类型的聚合。因为这是较老的代码,这些列表中的大多数是普通的TList或TStringList,持有无类型的指针。因此,对于每个复制命令,都会传入相应的类型以供正确使用。

procedure TPCEData.CopyPCEData(Dest: TPCEData);
begin
  Dest.Clear;

  <irrelevent stuff>

  CopyPCEItems(FVisitTypesList,  Dest.FVisitTypesList,  TPCEProc); //kt added
  CopyPCEItems(FDiagnoses,       Dest.FDiagnoses,       TPCEDiag);
  CopyPCEItems(FProcedures,      Dest.FProcedures,      TPCEProc);
  CopyPCEItems(FImmunizations,   Dest.FImmunizations,   TPCEImm);
  CopyPCEItems(FSkinTests,       Dest.FSkinTests,       TPCESkin);
  CopyPCEItems(FPatientEds,      Dest.FPatientEds,      TPCEPat);
  CopyPCEItems(FHealthFactors,   Dest.FHealthFactors,   TPCEHealth);
  CopyPCEItems(FExams,           Dest.FExams,           TPCEExams);

  <irrelevent stuff>
end;

此复制PCEItems如下所示:

procedure TPCEData.CopyPCEItems(Src: TList; Dest: TObject; ItemClass: TPCEItemClass);
var
  AItem: TPCEItem;
  i: Integer;
  IsStrings: boolean;
  Obj : TObject;

begin
  if (Dest is TStrings) then begin
    IsStrings := TRUE
  end else if (Dest is TList) then begin
    IsStrings := FALSE
  end else begin
    exit;
  end;
  for i := 0 to Src.Count - 1 do begin
    Obj := TObject(Src[i]);
    if(not TPCEItem(Src[i]).FDelete) then begin
      AItem := ItemClass.Create;  //<--- THE PROBLEMATIC LINE
      if (Obj.ClassType = TPCEProc) and (ItemClass = TPCEProc) then begin  //kt added if block and sub block below
        TPCEProc(Obj).CopyProc(TPCEProc(AItem));
      end else begin
        AItem.Assign(TPCEItem(Src[i]));  //kt <-- originally this line was by itself.
      end;
      if (IsStrings) then begin
        TStrings(Dest).AddObject(AItem.ItemStr, AItem)
      end else begin
        TList(Dest).Add(AItem);
      end;
    end;
  end;
end;

有问题的行如下:

AItem := ItemClass.Create;

当我使用调试器单步调试代码并停在这一行时,变量ItemClass的检查如下所示

ItemClass  = TPCEProc

问题是. Create调用的是TObject. Create,而不是TPCEProc. Create,这使我没有机会示例化一些需要的TStringList,稍后会导致访问冲突错误。
有谁能帮我弄明白这是怎么回事吗?我怀疑问题出在这行上:

TPCEItemClass = class of TPCEItem;

这是因为这是一个祖先类型(即TPCEItem)的类,所以它不能正确地携带子类型(TPCEProc)的信息。但是如果这是真的,那么为什么调试器显示ItemClass = TPCEProc?
如何实现对TPCEProc.Create的调用?
我已经用Delphi编程至少30年了,我一直遇到多态性的问题,这让我很沮丧。我反复阅读这方面的文章。但我一直碰壁。
先谢了。

l5tcr1uw

l5tcr1uw1#

当你通过元类构造对象时,你需要把它的基类构造函数标记为虚的,如果你需要在任何子类中有一个构造函数,它们需要重写那个虚构造函数。
如果基类没有构造函数,则需要添加一个空的构造函数。

TPCEItem = class(TObject)
  public
    constructor Create; virtual;
  end;

  TPCEItemClass = class of TPCEItem;

  TPCEProc = class(TPCEItem)
  public
    constructor Create; override;
    destructor Destroy; override;
  end;

constructor TPCEItem.Create;
begin
  // if the descendant class is TObject
  // or any other class that has empty constructor
  // you can omit inherited call
  inherited;
end;
ni65a41a

ni65a41a2#

您已经发现了问题-基类TPCEItem没有定义virtual构造函数,它只是从TObject继承了一个构造函数,而TObject不是virtual
因此,你不能使用TPCEItemClass元类类型创建任何TPCEItem派生类的示例.为了使元类调用正确的派生类构造函数,被引用的基类必须有virtual构造函数,例如:

TPCEItem = class(TObject)
  ...
  public
    constructor Create; virtual;
  end;
TPCEProc = class(TPCEItem)
  ...
  public
    constructor Create; override;
    ...
  end;
procedure TPCEData.CopyPCEItems(...; ItemClass: TPCEItemClass);
var
  AItem: TPCEItem;
  ...
begin
  ...
  AItem := ItemClass.Create; // <-- THIS WORKS NOW!
  ...   
  if (Obj is TPCEProc) then begin // <-- FYI: use 'is' rather than ClassType to handle descendants of TPCEProc...
    TPCEProc(Obj).CopyProc(TPCEProc(AItem));
  ...
end;
2nc8po8w

2nc8po8w3#

恭喜您已识别出有问题的行

AItem := ItemClass.Create;  //<--- THE PROBLEMATIC LINE

但是这一行有什么问题呢?你正在从现有的类示例中调用构造函数方法。你不应该这样做。你应该只从特定的类类型中调用构造函数方法,而不是从现有的类示例中调用。
因此,为了修复代码,请将上面提到的行更改为

AItem := TPCEItem.Create;

您可能正在考虑调用AItem := TPCEItemClass.Create;,因为您在上面的代码中做了next声明

TPCEItemClass = class of TPCEItem;

此声明并不意味着TPCEItemClassTPCEItem是相同的类型,而是这两种类型具有相同的类型结构,但它们实际上是两种不同的类型。
顺便问一下,如果你甚至没有在你的过程中使用CopyPCEItems参数,而是一直使用局部变量AItem: TPCEItem,那么ItemClass: TPCEItemClass参数的用途是什么?至少在你显示的代码中是这样的。

相关问题