英文:
How to make Delphi call correct constructor during dynamic creating?
问题
我有问题,似乎在动态创建时调用了不正确的构造函数。
我几乎与5年前提出了完全相同的问题(https://stackoverflow.com/questions/44165088/why-does-delphi-call-incorrect-constructor-during-dynamic-object-creation),我已经查看过了。但那个线程有覆盖虚拟调用的问题,我现在没有。我还尝试在StackOverflow上搜索匹配的问题,但找不到答案。
我正在处理遗留代码,所以我没有写这个代码的大部分部分。(如果你在下面看到带有"//kt"的注释,那就是我)
代码有一个基类TPCEItem
,如下所示。请注意,它没有构造函数。
TPCEItem = class(TObject)
{用于PCE项目的基类}
private
// 不相关的内容
public
// 不相关的内容
end;
接下来,有一个用于传递参数的类类型。
TPCEItemClass = class of TPCEItem;
接下来,有一个子类,如下所示。请注意,它有一个构造函数。编译器不允许我在这个Create
方法中添加"override",因为声明这个方法的祖先类(TObject
)没有将其定义为虚方法。
TPCEProc = class(TPCEItem)
{用于过程的类}
protected
// 不相关的内容
public
// 不相关的内容
constructor Create;
destructor Destroy; override;
end;
然后,代码有一个用于复制数据的函数,这是各种派生类型的综合体。由于这是旧代码,大多数这些列表都是普通的TList
或TStringList
,保存着无类型的指针。因此,为了正确使用每个复制命令,传递了相应的类型。
procedure TPCEData.CopyPCEData(Dest: TPCEData);
begin
Dest.Clear;
// 不相关的内容
CopyPCEItems(FVisitTypesList, Dest.FVisitTypesList, TPCEProc); //kt添加
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);
// 不相关的内容
end;
CopyPCEItems
如下所示:
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; //<--- 有问题的行
if (Obj.ClassType = TPCEProc) and (ItemClass = TPCEProc) then begin //kt添加的if块和下面的子块
TPCEProc(Obj).CopyProc(TPCEProc(AItem));
end else begin
AItem.Assign(TPCEItem(Src[i])); //kt <-- 最初这一行是独立的。
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
?
我在Delphi中编程已经至少30年了,但我仍然对多态性感到困惑。我已经反复阅读相关内容,但总是碰到难题。
提前感谢您的帮助。
英文:
I'm having problems with my Delphi 2006 seeming to call the incorrect constructor during dynamic creation.
I asked almost the exact same question 5 yrs ago (https://stackoverflow.com/questions/44165088/why-does-delphi-call-incorrect-constructor-during-dynamic-object-creation), and I have reviewed that. But that thread had issues of overriding virtual calls which I don't have now. I have also tried searching through StackOverflow for a matching question, but couldn't find an answer.
I am working with legacy code, so I didn't write much of this. (If you see comments below with '//kt' adding something, that is me).
The code has base class, TPCEItem as follow. Note that it does NOT have a constructor.
TPCEItem = class(TObject)
{base class for PCE items}
private
<irrelevent stuff>
public
<irrelevent stuff>
end;
Next, there is class type to use for passing a parameter (more below).
TPCEItemClass = class of TPCEItem;
Next I have a child class as follows. Note that it DOES have a contructor. The compiler will not allow me to add 'override' to this create method because the ancestor class where this is declared (TObject) does not define it as virtual.
TPCEProc = class(TPCEItem)
{class for procedures}
protected
<irrelevent stuff>
public
<irrelevent stuff>
constructor Create;
destructor Destroy; override;
end;
The code then has a function for copying data, which is a conglomeration of descendant types. Because this is older code, mosts of these lists are plain TLists or TStringLists, holding untyped pointers. Thus for each copy command a corresponding type is passed in for correct use.
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;
This CopyPCEItems is as follows:
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;
The problematic line is as below:
AItem := ItemClass.Create;
When I step through the code with the debugger, and stop on this line, an inspection of the variable ItemClass is as follows
ItemClass = TPCEProc
The problems is that the .Create is calling TObject.Create, not TPCEProc.Create, which doesn't give me an opportunity to instantiate some needed TStringLists, and later leads to access violation error.
Can anyone help me understand what is going on here? I have a suspicion that the problem is with this line:
TPCEItemClass = class of TPCEItem;
It is because this is of a class of an ancestor type (i.e. TPCEItem), that it doesn't properly carry the information for the child type (TPCEProc)?? But if this is true, then why does the debugger show that ItemClass = TPCEProc??
How can I effect a call to TPCEProc.Create?
I have been programming in Delphi for at least 30 yrs, and it frustrates me that I keep having problems with polymorphism. I have read about this repeatedly. But I keep hitting walls.
Thanks in advance.
答案1
得分: 2
当您通过元类构建对象时,需要将其基类构造函数标记为虚拟的,如果在任何派生类中需要构造函数,它们需要覆盖该虚拟构造函数。
如果基类没有构造函数,您需要添加一个空构造函数。
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
// 如果派生类是TObject
// 或者是具有空构造函数的任何其他类
// 您可以省略inherited调用
inherited;
end;
英文:
When you are constructing objects through meta-class you need to mark its base class constructor as virtual, and if you need a constructor in any of the descendant classes they need to override that virtual constructor.
If the base class does not have a constructor, you will need to add empty one.
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;
答案2
得分: 1
你已经确定了问题 - 基类 TPCEItem
没有定义一个virtual
构造函数,它只是继承了 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; // <-- 现在可以工作了!
...
if (Obj is TPCEProc) then begin // <-- 注意:使用 'is' 而不是 ClassType 来处理 TPCEProc 的派生类...
TPCEProc(Obj).CopyProc(TPCEProc(AItem));
...
end;
英文:
You have already identified the problem - the base class TPCEItem
does not define a virtual
constructor, it just inherits a constructor from TObject
, which is not virtual
.
As such, you cannot create instances of any TPCEItem
-derived classes by using your TPCEItemClass
metaclass type. In order for a metaclass to invoke the correct derived class constructor, the base class being referred to MUST have a virtual
constructor, eg:
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;
答案3
得分: -3
恭喜你已经找出了问题所在的那一行
AItem := ItemClass.Create; //<--- 有问题的那一行
但是,这一行有什么问题呢?你正在从现有的类实例中调用构造方法。你永远不应该这样做。你应该只从特定的类类型中调用构造方法,而不是现有的类实例。
所以,为了修复你的代码,请将上述的那行改成
AItem := TPCEItem.Create;
也许你可能在考虑调用 AItem := TPCEItemClass.Create;
,因为在你的代码中你做了下面的声明
TPCEItemClass = class of TPCEItem;
这个声明并不意味着 TPCEItemClass
和 TPCEItem
是相同的类型,而是这两个类型具有相同的类型结构,但实际上它们是两个不同的类型。
顺便问一下,你的 CopyPCEItems
过程的参数 ItemClass: TPCEItemClass
是什么目的?如果你在过程中根本没有使用它,而是一直在使用局部变量 AItem: TPCEItem
,那么它的存在有什么意义呢?至少在你展示的代码中是这样。
英文:
Congratulations you have identified the problematic line
AItem := ItemClass.Create; //<--- THE PROBLEMATIC LINE
But what is wrong with this line? You are calling constructor method from existing class instance. You should not do this ever. You should only call constructor methods from specific class types not existing class instances.
So in order to fix your code change the mentioned line to
AItem := TPCEItem.Create;
You may be thinking of perhaps calling AItem := TPCEItemClass.Create;
since above in your code you made next declaration
TPCEItemClass = class of TPCEItem;
This declaration does not meant that TPCEItemClass
is the same type as TPCEItem
but instead that both types have same type structure but they are in fact two distinct types.
By the way what is the purpose of ItemClass: TPCEItemClass
parameter of your CopyPCEItems
procedure if you are not even using it in your procedure but instead work with local variable AItem: TPCEItem
all the time? Well at least in your shown code that is.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论