8

I'm writing a generic vector type:

type
  TBigVector<T: record> = class
  private
    FSize: Integer;
    FEntries: TArray<T>;
    function GetEntry(Index: Integer): T; 
    procedure SetEntry(Index: Integer; const Value: T); 
    procedure SetSize(Value: Integer); 
  public
    constructor Create(ASize: Integer);
    property Size: Integer read FSize write SetSize;
    property Entry[Index: Integer]: T read GetEntry write SetEntry; default;
    procedure Zeroise;
    function ToArray: TArray<T>; 
  end;

I then want to derive classes like this:

TDoubleBigVector = class(TBigVector<Double>)
  ....
end;
TComplexBigVector = class(TBigVector<Complex>)
  ....
end;

I'm having to derive classes because I cannot implement arithmetic operations in the generic type. That is because generic constraints are not rich enough for me to constrain T to support the required arithmetic operations. Grr!

Anyway, enough of that. My question concerns the implementation of Zeroise and ToArray. For performance reasons, I want to use raw memory operations. For instance, Zeroise might be:

procedure TBigVector<T>.Zeroise;
begin
  ZeroMemory(Pointer(FEntries), Size*SizeOf(T));
end;

Now, I'm fine with doing that for types like Double and my bespoke Complex type. I know that they are not managed and a raw memory copy creates no difficulty. What I would like to do is add a runtime check, perhaps only called in my debug builds, that enforces the constraint that T has no managed types. How can I do that?

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • @whosrdaddy Yes. But I'd rather do it with type info methods in the same fashion as Delphi's RTL. I typically build with new style RTTI off. I'd be happy to see both flavours of answer. What serves my personal needs is one thing, but I suspect answers of different flavours could be valuable to others. – David Heffernan Feb 13 '14 at 12:33

1 Answers1

10

I would do it like this:

program SO21753006;

{$APPTYPE CONSOLE}

uses
  TypInfo,
  Windows,
  SysUtils;

type
  TProblemRecord1 = record
    I : Integer;
    S : String;
  end;

  TProblemRecord2 = record
    Obj : TObject;
  end;

  TGoodRecord = record
    I : Integer;
    K : Double;
    S : Array[0..10] of Char;
  end;


  TBigVector<T: record> = class
  private
    FSize: Integer;
    FEntries: TArray<T>;
    function GetEntry(Index: Integer): T;
    procedure SetEntry(Index: Integer; const Value: T);
    procedure SetSize(Value: Integer);
  public
    constructor Create(ASize: Integer);
    property Size: Integer read FSize write SetSize;
    property Entry[Index: Integer]: T read GetEntry write SetEntry; default;
    procedure Zeroise;
    function ToArray: TArray<T>;
  end;


function RecordHasNoManagedTypes(Typ : PTypeInfo) : Boolean;

var
 TypeData : PTypeData;

begin
 Assert(Assigned(Typ));
 Result := True;
 // only check if we have a record, or else we have a value type
 if Typ.Kind = tkRecord then
  begin
   TypeData := GetTypeData(Typ);
   Result := TypeData.ManagedFldCount = 0;
  end;
end;

{ TBigVector<T> }

constructor TBigVector<T>.Create(ASize: Integer);
begin
 Size := ASize;
end;

function TBigVector<T>.GetEntry(Index: Integer): T;
begin
end;

procedure TBigVector<T>.SetEntry(Index: Integer; const Value: T);
begin
end;

procedure TBigVector<T>.SetSize(Value: Integer);
begin
 SetLength(FEntries, Value);
end;

function TBigVector<T>.ToArray: TArray<T>;
begin
end;

procedure TBigVector<T>.Zeroise;
begin
 Assert(RecordHasNoManagedTypes(TypeInfo(T)), 'T must not have managed types!');
 ZeroMemory(Pointer(FEntries), Size*SizeOf(T));
end;

var
  Rec1 : TBigVector<Double>;
  Rec2 : TBigVector<TGoodRecord>;
  Rec3 : TBigVector<TProblemRecord1>;
  Rec4 : TBigVector<TProblemRecord2>;

begin
  try
    Writeln('Double type');
    Rec1 := TBigVector<Double>.Create(1);
    Rec1.Zeroise;
    Writeln('GoodRecord type');
    Rec2 := TBigVector<TGoodRecord>.Create(10);
    Rec2.Zeroise;
    try
     Writeln('Problemrecord1 type');
     Rec3 := TBigVector<TProblemRecord1>.Create(10);
     Rec3.Zeroise;
    except
     on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
    end;
    try
     Writeln('Problemrecord2 type');
     Rec4 := TBigVector<TProblemRecord2>.Create(10);
     Rec4.Zeroise;
    except
     on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
    end;
  except
   on E: Exception do
    Writeln(E.ClassName, ': ', E.Message);
  end;
  Readln;
end.

TObject for non-ARC platforms is not managed so the use ManagedFldCount is valid here.

UPDATE

As David pointed out, in more recent Delphi versions (Read: from D2010) you can use the Rtti.IsManaged function.

the code would look like this:

procedure TBigVector<T>.Zeroise;
begin
 Assert(not RTTI.IsManaged(TypeInfo(T)), 'T must not have managed types!');
 ZeroMemory(Pointer(FEntries), Size*SizeOf(T));
end;

UPDATE 2
From XE7 onwards you can use the intrinsic function System.IsManagedType(T). This will resolve at compile-time, incurring zero runtime overhead.

if IsManagedType(T) then Assert(false, 'T most not be a managed type');

Don't do Assert(not(IsManagedType(T)) because the compiler will not be able to remove the Assert, but it will eliminate the if if it does not apply.

Johan
  • 74,508
  • 24
  • 191
  • 319
whosrdaddy
  • 11,720
  • 4
  • 50
  • 99
  • Class types are unmanaged and there is no special compiler support for them. `Initialize` and `Finalize` are no-ops for them, and copying them just copies the raw bits. Dynamic arrays are managed. The behaviour you're seeing makes sense to me. –  Feb 13 '14 at 13:49
  • +1 Thanks. Your answer led me to `Rtti.IsManaged`. That might be a good service to rely on. – David Heffernan Feb 13 '14 at 13:50
  • Nice question and answer. Is there anybody out there to know why `System.Rtti.IsManaged` is not located in `System.TypInfo`? This function did not use any RTTI stuff ... – Sir Rufo Feb 13 '14 at 16:06
  • Managed types does not include `TObjects`, at least for non-arc platforms. See also [`What are managed types? Are they specific to Delphi? Are they specific to Windows?`](http://stackoverflow.com/a/5351597/576719). – LU RD Feb 13 '14 at 16:15
  • @whosrdaddy `TObject` for non-ARC platforms is not managed. So the use of `ManagedFldCount` is a perfect fit for the question. I think maybe you should edit the answer to reflect that. Then I would accept. – David Heffernan Feb 13 '14 at 16:21
  • I was just going to suggest to make the assertion inside a class constructor. Turns out generic class constructors are buggy. [`Delphi XE: class constructor doesn't get called in a class using generics`](http://stackoverflow.com/q/9501451/576719). – LU RD Feb 13 '14 at 19:30