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?
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With