6

I have this atomic optimistic initializer class:

type
  Atomic<T: IInterface> = class
    type TFactory = reference to function: T;
    class function Initialize(var storage: T; factory: TFactory): T;
  end;

class function Atomic<T>.Initialize(var storage: T; factory: TFactory): T;
var
  tmpIntf: T;
begin
  if not assigned(storage) then begin
    tmpIntf := factory();
    if InterlockedCompareExchangePointer(PPointer(@storage)^, PPointer(@tmpIntf)^, nil) = nil then
      PPointer(@tmpIntf)^ := nil;
  end;
  Result := storage;
end;

Now I would like to implement the same pattern for objects.

type
  Atomic<T: class> = class
    type TFactory = reference to function: T;
    class function Initialize(var storage: T; factory: TFactory): T;
  end;

class function Atomic<T>.Initialize(var storage: T; factory: TFactory): T;
var
  tmpIntf: T;
begin
  if not assigned(storage) then begin
    tmpIntf := factory();
    if InterlockedCompareExchangePointer(PPointer(@storage)^, PPointer(@tmpIntf)^, nil) = nil then
      tmpIntf.Free;
  end;
  Result := storage;
end;

I can do those two in two separate classes, but I would really like to put both initializers under the same umbrella. IOW, I would ideally like to use this as

var
  o: TObject;
  i: IInterface;

Atomic<TObject>.Initialize(o, CreateObject);
Atomic<IInterface>.Initialize(i, CreateInterface);

I can't find any good solution for this. The only idea I got is to declare class as Atomic<T> (without constraints) and then somehow (don't yet know how) check the RTTI of T in runtime and proceed accordingly.

I don't like this idea much and I'm looking for a better approach.

gabr
  • 26,580
  • 9
  • 75
  • 141

2 Answers2

4

It seems you cannot specify constraints of the type "class or interface". Therefore the easiest solution seems to be to drop the constraint (you can enforce it at runtime, using RTTI).

For the RTTI approach, you can use the TypeInfo function:

uses
  ..., TypInfo;    

class function Atomic<T>.Initialize(var storage: T; factory: TFactory): T;
var
  tmpT: T;
begin
  if not assigned(PPointer(@storage)^) then begin
    tmpT := factory();
    if InterlockedCompareExchangePointer(PPointer(@storage)^, PPointer(@tmpT)^, nil) = nil then begin
      case PTypeInfo(TypeInfo(T))^.Kind of
        tkInterface:
          PPointer(@tmpT)^ := nil;
        tkClass:
          TObject(tmpT).Free;
        else
          raise Exception.Create('Atomic<T>.Initialize: Unsupported type');
      end;
    end;
  end;
  Result := storage;
end;
gabr
  • 26,580
  • 9
  • 75
  • 141
Ondrej Kelle
  • 36,941
  • 2
  • 65
  • 128
4

One strongly-typed solution is to wrap both generic classes into another class to provide common namespace for the operation

type
  Atomic = class
    type
      Intf<T: IInterface> = class
        type TFactory = reference to function: T;
        class function Initialize(var storage: T; factory: TFactory): T;
      end;
      Obj<T: class> = class
        type TFactory = reference to function: T;
        class function Initialize(var storage: T; factory: TFactory): T;
      end;
  end;

Usage would then be:

var
  o: TObject;
  i: IInterface;

Atomic.Obj<TObject>.Initialize(o, CreateObject);
Atomic.Intf<IInterface>.Initialize(i, CreateInterface);
gabr
  • 26,580
  • 9
  • 75
  • 141