Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Delphi - Strange behavior with smart pointer constructors

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

like image 506
Jean-Milost Reymond Avatar asked Oct 21 '25 01:10

Jean-Milost Reymond


1 Answers

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?

like image 171
David Heffernan Avatar answered Oct 25 '25 10:10

David Heffernan



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!