As you know if you follow my blog, OmniThreadLibrary now offers a simple way to do optimistic and pessimistic atomic initialization which works for interfaces, objects and (in the case of the pessimistic initialization), anything else. [In case you missed those articles - I also discussed a comparison of both methods and wrote a short post about the third approach to initialization.]
A typical usage of both types of initialization would be:
var sl: TStringList; ol: Locked<TObjectList>; Atomic<TStringList>.Initialize(sl, function: TStringList begin Result := TStringList.Create; end); ol.Initialize( function: TObjectList begin Result := TObjectList.Create; end);
As you can see, this is pretty long-winded. If you are initializing an interface, then you’ll usually have written a factory method already and the code would be much simpler (example below this paragraph) but in the case of objects this is not very typical.
So I thought – both Atomic and Locked already know what entity type they wrap around so calling a constructor from inside Initialize would be trivial, eh? I could then write a simplified version
and use the longer version only when needed, for example to initialize interfaces or to call a non-default object constructor. What could possibly go wrong?
class function Atomic<T>.Initialize(var storage: T): T; begin if PTypeInfo(TypeInfo(T))^.Kind <> tkClass then raise Exception.Create('Atomic<T>.Initialize: Unsupported type'); Result := Atomic<T>.Initialize(storage, function: T begin Result := T.Create; end); end;
… and my plans were immediately thwarted.
[DCC Error] OtlSync.pas(671): E2003 Undeclared identifier: 'Create'
Why oh why can’t Delphi call the constructor? Well, because I cheated and declared the Atomic type without any constraints.
Atomic<T> = class
To call the constructor I should constrain the T type to be a class with a constructor.
Atomic<T:class,constructor> = class
So the next question is – why I cheated? It’s simple – because I wanted Atomic to wrap both objects and interfaces and in Delphi one cannot set a generic type constraint to be a union of type. This would be great but sadly it doesn’t work (it would also introduce many problems so I’m not even bothering to propose a language change in this direction):
Atomic<T: class, constructor or IInterface> = class
Great thanks to [TOndrej] who helped me write an unconstrained generic which checks the type of T at runtime.
As usual, there’s a solution in Delphi for everything, except this time I was not smart enough to find it. Again, StackOverflow came to the rescue (and again, [TOndrej] was the hero of the day). His solution was to use
Result := T(GetTypeData(PTypeInfo(TypeInfo(T)))^.ClassType.Create);
instead of Result := T.Create;.
This worked just fine – in XE. And XE2 (original release). And XE2 Update 1. But it failed in XE2 Update 2 where casting to T was suddenly not allowed anymore. Of course, this again only fails for unconstrained types but, as I said before, I really really want T to be unconstrained in this case.
For some time it looked like I’ll have to scrap my plans for simplified initializers, but then another great mind stepped ahead and provided XE2 Update 2 compatible solution in the OmniThreadLibrary forum., Thanks, [cjsalamon]!
class function Atomic<T>.Initialize(var storage: T): T; begin Result := Atomic<T>.Initialize(storage, function: T var inValue : TValue; outValue: TValue; begin inValue := GetTypeData(PTypeInfo(TypeInfo(T)))^.ClassType.Create; inValue.TryCast(TypeInfo(T), outValue); Result := outValue.AsType<T>; end); end;
This solution uses TValue (which in turn uses Extended RTTI) to do the casting. Ugly, slow, but this doesn’t matter as the initialization of shared resources typically occurs very rarely. Plus you can always use the version with the explicit resource factory if you really need object creation to be swift.
After publishing this article, [Sergey] noted in comments that the method is flawed. [TOndrej]’s hack always calls TObject.Create and not the constructor from the T class, which makes it pretty much unusable :(
Luckily, there’s a solution for that too – use ERTTI to enumerate all T’s methods, find first parameterless constructor and execute it. It was posted in the already mentioned StackOverflow question and is now the accepted solution for the problem. Thanks, [Linas]!
So at the end my plans (again!) came to the happy conclusion thanks to great programmers all around the world. Thanks again, guys!