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?
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.
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