How to make Delphi call correct constructor during dynamic creating? - delphi

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 (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.

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;

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;

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.

Related

Best way to implement Clone() in my specific classes

In my specific TPersistent classes I'd like to provide a Clone function, which returns an independent copy of the object.
Is it possible to make this work correctly with descendants, without implementing the Clone function in each and every descendant?
This is not about cloning any unknown fields or deep cloning (which could be done using RTTI). In my minimal example below, you can see where I would want to put the Clone function.
Since it uses Assign() to copy the data, it would work with any descendant. The problem is the constructor, see comments. How do I call the correct constructor of that descendant? If that's very hard to do, it's okay to assume that none of the descendants override the constructor without overriding Clone, too.
program Test;
uses System.SysUtils, System.Classes;
type
TMyClassBase = class abstract(TPersistent)
public
constructor Create; virtual; abstract;
function Clone: TMyClassBase; virtual; abstract;
end;
TMyClassBase<T> = class abstract(TMyClassBase)
private
FValue: T;
public
constructor Create; overload; override;
function Clone: TMyClassBase; override;
procedure Assign(Source: TPersistent); override;
property Value: T read FValue write FValue;
end;
TMyClassInt = class(TMyClassBase<Integer>)
public
function ToString: string; override;
end;
TMyClassStr = class(TMyClassBase<string>)
public
function ToString: string; override;
end;
constructor TMyClassBase<T>.Create;
begin
Writeln('some necessary initialization');
end;
procedure TMyClassBase<T>.Assign(Source: TPersistent);
begin
if Source is TMyClassBase<T> then FValue:= (Source as TMyClassBase<T>).FValue
else inherited;
end;
function TMyClassBase<T>.Clone: TMyClassBase;
begin
{the following works, but it calls TObject.Create!}
Result:= ClassType.Create as TMyClassBase<T>;
Result.Assign(Self);
end;
function TMyClassInt.ToString: string;
begin
Result:= FValue.ToString;
end;
function TMyClassStr.ToString: string;
begin
Result:= FValue;
end;
var
ObjInt: TMyClassInt;
ObjBase: TMyClassBase;
begin
ObjInt:= TMyClassInt.Create;
ObjInt.Value:= 42;
ObjBase:= ObjInt.Clone;
Writeln(ObjBase.ToString);
Readln;
ObjInt.Free;
ObjBase.Free;
end.
The output is
some necessary initialization
42
So, the correct class came out, it works correctly in this minimal example, but unfortunately, my necessary initialization wasn't done (should appear twice).
I hope I could make it clear and you like my example code :) - I'd also appreciate any other comments or improvements. Is my Assign() implementation ok?
You don't need to make the non-generic base class constructor abstract. You can implement the clone there, because you have a virtual constructor.
Furthermore you don't need to make the Clone method virtual.
type
TMyClassBase = class abstract(TPersistent)
public
constructor Create; virtual; abstract;
function Clone: TMyClassBase;
end;
...
type
TMyClassBaseClass = class of TMyClassBase;
function TMyClassBase.Clone: TMyClassBase;
begin
Result := TMyClassBaseClass(ClassType).Create;
try
Result.Assign(Self);
except
Result.DisposeOf;
raise;
end;
end;
Note that ClassType returns TClass. We cast it to TMyClassBaseClass to make sure that we call your base class virtual constructor.
I also don't see why you made TMyClassBase<T> abstract and derived specifications from it. You should be able to implement everything you need in the generic class.
This seems to do it:
function TMyClassBase<T>.Clone: TMyClassBase;
begin
{create new instance using empty TObject constructor}
Result:= ClassType.Create as TMyClassBase<T>;
{execute correct virtual constructor of descendant on instance}
Result.Create;
{copy data to instance}
Result.Assign(Self);
end;
However, I've never seen that before - it feels very, very wrong...
I verified that it correctly initializes data of the target object and really calls the descendants constructor once. I see no problem, there is also no memory leak reported. Tested using Delphi 10.2.2. Please comment :)

How to define a base class which must be inherited?

I have created a component which its self should never be instantiated, but instead it must be inherited. Similar concept to the TThread. What can I do to this component to ensure it's never instantiated by its self without being inherited? For example, when the object is instantiated, raise an exception that the class is required to be inherited, or if possible, don't even let any project compile where an instance of this component base is attempted.
In XE2, you can declare the class as abstract:
type TMyclass = class abstract (TAncestor)
Upd: it seems that Delphi still allows creating abstract classes (although documentation for some versions says it doesn't). Compiler should give a warning though.
Probably your class has some virtual method that has to be overridden (and that's why it must be inherited from). If so, just make the method abstract, and you will get an exception when it's invoked in the base class.
This was mentioned in the comments by TLama, but was never made an answer, so I'll answer this.
type
TEvilClass = class
public
constructor Create;
end;
TGoodClass = class(TEvilClass)
end;
{ TEvilClass }
constructor TEvilClass.Create;
begin
if ClassType = TEvilClass then
raise Exception.Create('I''m the evil class which cannot be instantiated!');
end;
procedure TForm1.Button1Click(Sender: TObject);
var
EvilClass: TEvilClass;
begin
EvilClass := TEvilClass.Create;
end;
procedure TForm1.Button2Click(Sender: TObject);
var
GoodClass: TGoodClass;
begin
GoodClass := TGoodClass.Create;
end;
This scenario is also one which makes sense to create your own exception type..
type
EvilClassException = Exception;
TEvilClass = class(TComponent)
public
constructor Create(AOwner: TComponent); override;
end;
...
constructor TEvilClass.Create(AOwner: TComponent);
begin
inherited;
if ClassType = TEvilClass then
raise EvilClassException.Create('I''m the evil class which cannot be instantiated!');
end;

Delphi XE2 - Inheriting class not calling base class' constructor...?

When creating a class that inherits from another class, shouldn't it be true that when the derived class is created the base classes's constructor is called?
Type
TBase = Class
constructor xMain;
End;
TDerived = Class(TBase)
constructor xMain;
End;
constructor TBase.xMain;
begin
MessageBox(0,'TBase','TBase',0);
end;
constructor TDerived.xMain;
begin
MessageBox(0,'TDerived','TDerived',0);
end;
Var
xTClass:TDerived;
begin
xTClass := TDerived.xMain;
end.
I thought this should result in a MessageBox displaying "TBase" and then "TDerived". Yet, this is not the case. When the above code is ran it only results in one MessageBox displaying "TDerived".
constructor TDerived.xMain;
begin
inherited;
MessageBox(0,'TDerived','TDerived',0);
end;
add inherited in TDerived.xMain; otherwise the code from ancestor will not be called;
begin
inherited;//call the ancestor TBase.xMain
MessageBox(0,'TDerived','TDerived',0);
end;
Also this question will help you understand inherited reserved word:
Delphi: How to call inherited inherited ancestor on a virtual method?
another good resource is http://www.delphibasics.co.uk/RTL.asp?Name=Inherited

Use virtual constructor to reset to initial state

i do not have any experience with virtual constructors which are available in Delphi. I consider to use virtual ctors in a class hierachy to reset the instance to an initial state like this:
A = class
end;
B = class(A)
end;
C = class(B)
end;
FooA = class
a_ : A;
constructor Create(inst : A); overload;
constructor Create; overload; virtual; abstract;
destructor Destroy; override;
function Bar : A;
end;
FooB = class(FooA)
b_ : B;
constructor Create; override;
constructor Create(inst : B); overload;
end;
FooC = class(FooB)
// ...
end;
{ FooA }
constructor FooA.Create(inst: A);
begin
inherited Create;
a_ := inst;
end;
destructor FooA.Destroy;
begin
FreeAndNil(a_);
inherited;
end;
function FooA.Bar : A;
begin
Result := a_;
a_ := nil;
// here comes the magic
Self.Create;
end;
{ FooB }
constructor FooB.Create;
begin
b_ := B.Create;
inherited Create(b_);
end;
constructor FooB.Create(inst: B);
begin
inherited Create(inst);
b_ := inst;
end;
{ FooC } // ...
var
fc : FooA;
baz : A;
begin
fc := FooC.Create;
baz := fc.Bar;
WriteLn(baz.ClassName);
FreeAndNil(baz);
FreeAndNil(fc);
ReadLn;
end.
Are there any problems/pitfalls in this design? The simple example works like a charm but i feel a little bit uneasy calling constructors (which do not construct anything) like this.
Edit:
I decided to move the initialization to a method in protected area with a meaningful name, what makes me feel better ;-)
FooA = class
strict private
a_ : A;
strict protected
procedure SetInst; overload; virtual; abstract;
procedure SetInst(i : A); overload;
public
constructor Create;
destructor Destroy; override;
function Foo : A;
end;
Very few classes are written to support the use of constructors as re-initializers. They usually assume that any dynamically allocated memory has not already been allocated. If you're in control of all the classes you're using, then go ahead and carefully use constructors as re-initializers.
Even if you're in control, I'd still advise against it. It's not idiomatic Delphi; anyone else reading your code (perhaps even you, a few weeks or months from now) will be confused — at least at first — by your non-standard use of constructors. It's not worth the trouble. If calling the Bar function is supposed to release ownership of the A object and create a new instance, then write functions with names that make that clear.
Rob's right about this being really weird-looking code that's likely to confuse people, and moving your code to an initialization routine is a good idea. In case you were wondering, the main purpose of virtual constructors is for something completely different: to more easily support "factory" style object creation.
Some outside source provides some data that can identify any descendant of a base class, and the factory uses a class reference and calls a virtual constructor defined in the base class on it. That way you end up with an instance of the descendant class without having to hard-code knowledge of the descendant class into the factory code.
If this sounds a bit strange, take a look at a DFM file. It's got a list of form objects that descend from TComponent, with their published properties. When the form reading code comes across an object statement, it reads the class name, looks it up in a table that maps class names to class references, and calls the virtual TComponent.Create on that class reference. This calls the virtual constructor for the actual class, and it ends up with an instance of that type of component, and starts to fill in its properties.

Delphi: writing to the private ancestor's field in descendant class

I need to fix a third-party component. This component's class has private variable which is actively used by its descendants:
TThirdPartyComponentBase = class
private
FSomeVar: Integer;
public
...
end;
TThirdPartyComponent = class (TThirdPartyComponentBase)
protected
procedure Foo; virtual;
end;
procedure TThirdPartyComponent.Foo;
begin
FSomeVar := 1; // ACCESSING PRIVATE FIELD!
end;
This works because both classes are in the same unit, so they're kinda "friends".
But if I'll try to create a new class in a new unit
TMyFixedComponent = class (TThirdPartyComponent)
procedure Foo; override;
end;
I can't access FSomeVar anymore, but I need to use it for my fix. And I really don't want to reproduce in my code all that tree of base classes.
Can you advise some quick hack to access that private field without changing the original component's unit if it's possible at all?
By the use of class helpers it's possible to accomplish access to the private parts of the base class from the derived class without loosing type safety.
Just add these declarations in another unit:
Uses YourThirdPartyComponent;
type
// A helper to the base class to expose FSomeVar
TMyBaseHelper = class helper for TThirdPartyComponentBase
private
procedure SetSomeVar( value : integer);
function GetSomeVar: integer;
public
property SomeVar:integer read GetSomeVar write SetSomeVar;
end;
TMyFixedComponent = class helper for TThirdPartyComponent
protected
procedure Foo;
end;
procedure TMyFixedComponent.Foo;
begin
// Cast to base class and by the class helper TMyBaseHelper the access is resolved
TThirdPartyComponentBase(Self).SomeVar := 1;
end;
function TMyBaseHelper.GetSomeVar: integer;
begin
Result := Self.FSomeVar; // ACCESSING PRIVATE FIELD!
end;
procedure TMyBaseHelper.SetSomeVar(value: integer);
begin
Self.FSomeVar := value; // ACCESSING PRIVATE FIELD!
end;
// Testing
var
TSV: TThirdPartyComponent;
begin
TSV := TThirdPartyComponent.Create;
try
TSV.Foo;
WriteLn(IntToStr(TSV.SomeVar)); // Writes 1
finally
TSV.Free;
end;
end.
As can be seen from comments in code, FSomeVar is exposed by a class helper from the TThirdPartyComponentBase class.
Another class helper for the TThirdPartyComponent implements the Foo procedure. In there, access to the SomeVar property of the base class helper is made via a type cast to the base class.
You have to use a hack to access a private field in any class (including a base class) in a different unit. In your case define in your unit:
type
__TThirdPartyComponentBase = class
private
FSomeVar: Integer;
end;
Then get the access:
__TThirdPartyComponentBase(Self).FSomeVar := 123;
Of course, that is dangerous, because you will need to control changes in the base class. Because if the fields layout will be changed and you will miss this fact, then the above approach will lead to failures, AV's, etc.
Don't know if this will help, but I seem to recall there is a way to "crack" a private variable into visibility.
I know, for example, I've encountered warnings from the compiler when I've moved a property from lower visibility (in the base class) to a more visible level (in my descendant). The warning stated that it's being declared at a different level of visibility...
It's been some time and I'm not certain, but I believe what you can do is in your descendant declare the same variable as protected. (You may have to use the Redeclare keyword for this to compile.)
Sorry I don't have more specific information on how to do this (if it's indeed possible.) Perhaps this posting will prompt one of the wizards here into correcting me! :-)
Expose the value of the private variable by a protected property in TThirdPartyComponent.
TThirdPartyComponent = class (TThirdPartyComponentBase)
private
Procedure SetValue(Value: Integer);
Function GetValue: Integer;
protected
Property MyVar: Integer read GetValue write Setvalue;
procedure Foo; virtual;
end;
Procedure TThirdPartyComponent.SetValue(Value: Integer);
begin
FSomeVar := Value ;
end;
Function GetValue: Integer;
begin
result := FSomeVar;
end;
In TMyFixedComponent class use the MyVar Property in the procedure which you would like to override.

Resources