I'm working on a project containing several packages. In one of my base packages I declare a smart pointer, like that (here is the complete code):
unit UTWSmartPointer;
interface
type
IWSmartPointer<T> = reference to function: T;
TWSmartPointer<T: class, constructor> = class(TInterfacedObject, IWSmartPointer<T>)
private
m_pInstance: T;
public
constructor Create; overload; virtual;
constructor Create(pInstance: T); overload; virtual;
destructor Destroy; override;
function Invoke: T; virtual;
end;
implementation
//---------------------------------------------------------------------------
constructor TWSmartPointer<T>.Create;
begin
inherited Create;
m_pInstance := T.Create;
end;
//---------------------------------------------------------------------------
constructor TWSmartPointer<T>.Create(pInstance: T);
begin
inherited Create;
m_pInstance := pInstance;
end;
//---------------------------------------------------------------------------
destructor TWSmartPointer<T>.Destroy;
begin
m_pInstance.Free;
m_pInstance := nil;
inherited Destroy;
end;
//---------------------------------------------------------------------------
function TWSmartPointer<T>.Invoke: T;
begin
Result := m_pInstance;
end;
//---------------------------------------------------------------------------
end.
Later in my project (and in another package), I use this smart pointer with a GDI+ object (a TGpGraphicsPath). I declare the graphic path like that:
...
pGraphicsPath: IWSmartPointer<TGpGraphicsPath>;
...
pGraphicsPath := TWSmartPointer<TGpGraphicsPath>.Create();
...
However, nothing is drawn on the screen when I execute the code. I get no error, no exception or access violation, just a blank page. But if I just change my code like that:
...
pGraphicsPath: IWSmartPointer<TGpGraphicsPath>;
...
pGraphicsPath := TWSmartPointer<TGpGraphicsPath>.Create(TGpGraphicsPath.Create);
...
then all become fine, and my path is painted exactly as expected. But I cannot figure out why the first constructor does not work as expected. Somebody can explain to me this strange behavior?
Regards
This is quite a complex trap that you have fallen into. When you write:
TGpGraphicsPath.Create
you might think that you are calling the parameterless constructor. But it is not so. You are in fact calling this constructor:
constructor Create(fillMode: TFillMode = FillModeAlternate); reintroduce; overload;
You supply no argument, so the default value is provided by the compiler.
In your smart pointer class you write:
T.Create
This really is calling the parameterless constructor. But that is the constructor defined by TObject. When that constructor is used, the TGPGraphicsPath instance is not properly initialised.
If you are going to use the constructor generic constraint, you must also ensure that you always use a class that can be properly constructed with a parameterless constructor. Unfortunately for you TGPGraphicsPath does not fit the bill. Indeed there are a preponderance of such classes.
There's really not a whole lot that you can do here to avoid explicitly calling the constructor. It's pretty much impossible for your smart pointer class to work out which constructor to call, for this particular class.
My advice would be to steer away from the constructor generic constraint and force the consumer of the smart pointer class to explicitly instantiate the instance.
This is quite a common issue – I answered a similar question here less than a week ago: Why does a deserialized TDictionary not work correctly?
Related
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.
I have an object that is derived from the TStringList object that I call a "TAutoString." It allows you to specify an object type when the list is created. Then each time a new entry is added to the string list, it also creates a copy of the object associated with that string entry. This makes it easy to store all kinds of additional information along with each string. For example:
type TMyObject = class(TObject)
public
Cats: integer;
Dogs: integer;
Mice: integer;
end;
MO := TAutoString.Create(TMyObject);
Inside the object, the class information is stored in a class variable:
private
ObjectClass: TClass;
constructor TAutoString.Create(ObjClass: TClass);
begin
inherited Create;
ObjectClass:=ObjClass;
end;
Now, every time a new item is added, it creates a new object of the specified type:
function TAutoString.Add(const S: string): Integer;
begin
Result:=inherited Add(S);
Objects[Result]:=ObjectClass.Create;
end;
I can now add or read information associated with each string entry.
TMyObject(MO.Objects[25]).Cats := 17;
D:=TMyObject(MO.Objects[25]).Dogs;
This works great as along as the object doesn't have a constructor. If the object has a constructor, its constructor won't get called when the object is created because the constructor for TObject is not virtual.
Can anyone think of a way around this problem. I've seen solutions that use the RTTI libraries, but this is in Delphi-7, which doesn't have an RTTI library.
As an aside, it seems a bit strange that TObject's constructor is not virtual. If it were, it would enable all sorts of useful features like the one I'm trying to implement.
EDIT: Remy's suggestion below was just the nudge I needed. I had originally tried a similar strategy, but I couldn't make it work. When it didn't seem to work the way I thought it should, I assumed there must be something that I didn't understand about virtual methods. His post pushed me to look at it again. It turned out that I had left off the "Override" directive for the constructor of the object I wanted to attach. Now it works just the way it should.
The other issue I was concerned about was that I had already used the Auto Strings in a bunch of other applications where the object was based on "TObject" and I didn't want to go back and change all that code. I solved that issue by overloading the constructors and having one for TObject-based objects and another my TAutoClass objects:
constructor Create(ObjClass: TAutoClass); overload; virtual;
constructor Create(ObjClass: TClass); overload; virtual;
Depending on which constructor is called, the object class stored in a different in a different variable.
private
AutoClass: TAutoClass;
ObjectClass: TClass;
Then when the object is constructed I check to see which has been assigned and use that one:
procedure TAutoString.CreateClassInstance(Index: integer);
begin
if AutoClass<>nil then Objects[Index]:=AutoClass.Create
else Objects[Index]:=ObjectClass.Create
end;
The new version works perfectly with either type of object.
To do what you want, you will have to define a base class for your list objects to derive from, and then you can add a virtual constructor to that class. Your ObjectClass member will have to use that class type instead of using TClass.
For example:
type
TAutoStringObject = class(TObject)
public
constructor Create; virtual;
end;
TAutoStringObjectClass = class of TAutoStringObject;
TAutoString = class(TStringList)
private
ObjectClass: TAutoStringObjectClass;
public
constructor Create(ObjClass: TAutoStringObjectClass);
function Add(const S: string): Integer; override;
...
end;
...
constructor TAutoStringObject.Create;
begin
inherited Create;
end;
constructor TAutoString.Create(ObjClass: TAutoStringObjectClass);
begin
inherited Create;
ObjectClass := ObjClass;
end;
function TAutoString.Add(const S: string): Integer;
var
Obj: TAutoStringObject;
begin
Obj := ObjectClass.Create;
try
Result := inherited AddObject(S, Obj);
except
Obj.Free;
raise;
end;
end;
...
Then, you simply adjust your derived object classes to use TAutoStringObject instead of TObject, eg:
type
TMyObject = class(TAutoStringObject)
public
...
constructor Create; override;
end;
MO := TAutoString.Create(TMyObject);
...
And their constructor will be called, as expected.
Here is a hint for a cleaner solution:
It is possible to add a virtual constructor to Tobject.
To do so you will need to use what is called a "class helper".
Here is an example:
type
TobjectHelper = class helper for Tobject
public
constructor Create; virtual; // adds a virtual constructor to Tobject.
end;
(My use case was a TryCreateObject function to detect out of memory situation during object creation, wraps try except end and simply returns true/false to prevent try except blocks in code and instead use more logic controleable if statements)
Class helpers were apperently(?) introduced in Delphi 8 and later. Your requirements is for Delphi 7 so this may not work.
Unless you coding for Windows 95/Windows 98/Windows XP it may be time for you to upgrade to a more recent version of Delphi, especially the Delphi XE versions for unicode support, otherwise you coding against an aging platform that is about to become obsolete etc.
However for Windows 95/Windows 98 and Windows XP I do believe Delphi 2007 may be of some use, I believe it can compile code that can run on those older windows platforms, I could be wrong though.
Later versions of Delphi require certain windows system DLLs to be present otherwise the build/compiled executable will not run, w95/w98/wxp lack these dlls.
I'm puzzled: why calling a Delphi constructor explicitly / as an ordinary method would not create a new instance / why no memory leaks?
Here's some sample code:
TMyHelperClass = class(TObject)
private
fSomeHelperInt: integer;
public
property SomeHelperInt : integer read fSomeHelperInt write fSomeHelperInt;
constructor Create (const initSomeHelperInt : integer);
end;
TMyClass = class(TObject)
private
fHelper : TMyHelperClass;
public
constructor Create(const initSomeInt: integer);
destructor Destroy; override;
property Helper : TMyHelperClass read fHelper;
end;
Implementation:
constructor TMyHelperClass.Create(const initSomeHelperInt: integer);
begin
fSomeHelperInt := initSomeHelperInt;
end;
constructor TMyClass.Create(const initSomeInt: integer);
begin
fHelper := TMyHelperClass.Create(initSomeInt);
end;
destructor TMyClass.Destroy;
begin
fHelper.Free;
inherited;
end;
And usage:
var
my : TMyClass;
begin
my := TMyClass.Create(2016);
try
//WHY is this ok to be used ?
my.Helper.Create(2017);
finally
my.Free;
end;
end;
How come I could call the TMyHelperClass+s Create constructor as an ordinary method? I mean - this IS exactly as I want it - but how come no issues (with memory)?
I guess the answer will be because the Create method was not called like TMyHelperClass.Create (to create an instance of TMyHelperClass)?
Is this way of calling the constructor as an ordinary method acceptable / ok to be used?
Yes you can call the constructor as an ordinary method.
But it is bad practice to do so.
why no memory leaks?
From: http://docwiki.embarcadero.com/RADStudio/Rio/en/Methods#Constructors
When a constructor is called using an object reference (rather than a
class reference), it does not create an object. Instead, the
constructor operates on the specified object, executing only the
statements in the constructor's implementation, and then returns a
reference to the object. A constructor is typically invoked on an
object reference in conjunction with the reserved word inherited to
execute an inherited constructor.
The compiler will generate different code when calling TObject.Create (class reference) vs AObject.Create (instance reference).
Anti-pattern warning
Abusing the constructor as a normal method will lead to problems when allocating resources.
Normally constructors and destructors are matched, but if you call the constructor twice (as you must when calling the constructor of an instance) you'll allocate the resource twice, but free it only once.
If you want to call the body of the constructor as a normal method, create a new method and call that method in the constructor.
E.g.:
constructor TTest.Create;
begin
inherited;
//Allocate needed resources here.
Init;
end;
procedure TTest.Init;
begin
//Do initialization stuff
//But don't allocate any resources
Now you can safely call the init method whenever needed without any chance of mishaps.
The reason that it's possible to call a constructor without "constructing" anything is that the following code must work:
constructor TTest.Create(const AObject: TObject);
begin //constructor of TTest happens here
inherited Create; //instance call to TParent.Create;
//Other code
end;
In very rare cases you can skip the class call to the constructor entirely.
The following code will work:
MyTest:= TTest.NewInstance;
//Voodoo code here.
MyTest.Create;
This can be used to prevent the compiler from inserting the automatic code that gets generated in a call to TTest.Create.
This is very rarely needed and in 20 years of coding I've only ever used it once.
The use case for this was speed sensitive code where I wanted to avoid the overhead of exception checking and zeroing out of allocated space, this was before Delphi supported records with methods.
If I had to do that code again I'd use a record and just allocate a pool of 1000 records on the heap and hand them out as needed.
Suppose in Delphi you have these classes:
type
TClass1 = class
public
constructor Create;
end;
TClass2 = class(TClass1)
public
constructor Create;
end;
TClass3 = class(TClass2)
public
constructor Create;
end;
Note that TClass1.Create is not virtual, and that TClass2 and TClass3 declare a constructor which is not virtual.
Suppose that I want to invoke TClass1's Create constructor-method, from within TClass3.Create, but not invoke the constructor-code in TClass2.Create? Is this possible within the language without recourse to RTTI?
I don't think there is such a syntax, but what I want is:
constructor TClass3.Create;
begin
super inherited Create; // Invoke TClass1.Create
end;
The closest I can get is this which compiles but just leaks an object, as it's doing a separate TClass1.Create construction.
constructor TClass3.Create;
begin
TClass1.Create; // returns new TClass1, discards and leaks it.
// other initialization here.
end;
It also seems to me that the code TClass1.Create invocation within TClass3.Create compiles, I cannot call it correct, it is wrong because it leaks an object. What is the correct right way to do it?
Update Note that David's answer works for a class hiearchy without virtual constructors, only, as I originally asked. His answer would not work in your code, if you had virtual constructors and TClass2 and TClass3 overrode them. If I had asked the above question with virtual constructors (or a virtual method that is not a constructor) the answer would be "you can't do it at all, except by really gross Virtual Method Table hacks". Also note that the linked "possible duplicate" is not a duplicate because the answer changes when you add/subtract virtual methods from the situation.
There is no syntactical support for skipping a layer of the inheritance hierarchy. The only way you can do what you want is like this:
TClass1(Self).Create;
A complete example program to demonstrate:
type
TClass1 = class
constructor Create;
end;
TClass2 = class(TClass1)
constructor Create;
end;
TClass3 = class(TClass2)
constructor Create;
end;
constructor TClass1.Create;
begin
Writeln('TClass1');
end;
constructor TClass2.Create;
begin
inherited;
Writeln('TClass2');
end;
constructor TClass3.Create;
begin
TClass1(Self).Create;
Writeln('TClass3');
end;
begin
TClass3.Create;
Readln;
end.
Output
TClass1
TClass3
While you should not do this you actually can achieve it with inline assembly code:
constructor TClass3.Create;
begin
asm
mov eax, Self
call TClass1.Create;
end;
Writeln('TClass3');
end;
But keep in mind that this is actually different from a theoretical super inherited (which would skip one inheritance level) while this just calls the said method. So if you introduce another inheritance level TClass2b between TClass2 and TClass3 it will skip that aswell.
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.