可以在 TQueue 中存储数组吗?
Store array in TQueue possible?
在 TQueue 中存储数组时出现问题。知道我哪里出错了吗?
代码在 Delphi XE 5 中运行良好,但在 Delphi 10 Seattle 中运行不正常。
(我无法确定这是否是错误或它应该如何工作。尝试在 embarcadero 中搜索线索但失败了。)
procedure TForm1.Button1Click(Sender: TObject);
var
FData: TQueue<TBytes>;
FsData: TQueue<String>;
arr: TBytes;
begin
FData := TQueue<TBytes>.Create;
FsData := TQueue<String>.Create;
try
setlength(arr, 3);
arr[0] := 1;
arr[1] := 2;
arr[2] := 3;
FData.Enqueue(arr);
Memo1.Lines.Add('Count, array:' + IntToStr(FData.Count)); // 0?
FsData.Enqueue('asada');
Memo1.Lines.Add('Count, string:' + IntToStr(FsData.Count)); // 1
finally
FData.Free;
FsData.Free;
end;
end;
这是 XE8 中引入的缺陷。这是我能制作的最简单的复制品。
{$APPTYPE CONSOLE}
uses
System.Generics.Collections;
var
Queue: TQueue<TArray<Byte>>;
begin
Queue := TQueue<TArray<Byte>>.Create;
Queue.Enqueue(nil);
Writeln(Queue.Count);
end.
在 XE7 中输出为 1,在 XE8 和 Seattle 中输出为 0。
这已报告给 Embarcadero:RSP-13196。
Enqueue
的实现如下所示:
procedure TQueue<T>.Enqueue(const Value: T);
begin
if IsManagedType(T) then
if (SizeOf(T) = SizeOf(Pointer)) and (GetTypeKind(T) <> tkRecord) then
FQueueHelper.InternalEnqueueMRef(Value, GetTypeKind(T))
else
FQueueHelper.InternalEnqueueManaged(Value)
else
case SizeOf(T) of
1: FQueueHelper.InternalEnqueue1(Value);
2: FQueueHelper.InternalEnqueue2(Value);
4: FQueueHelper.InternalEnqueue4(Value);
8: FQueueHelper.InternalEnqueue8(Value);
else
FQueueHelper.InternalEnqueueN(Value);
end;
end;
当T
为动态数组时,选择FQueueHelper.InternalEnqueueMRef
分支。这又看起来像这样:
procedure TQueueHelper.InternalEnqueueMRef(const Value; Kind: TTypeKind);
begin
case Kind of
TTypeKind.tkUString: InternalEnqueueString(Value);
TTypeKind.tkInterface: InternalEnqueueInterface(Value);
{$IF not Defined(NEXTGEN)}
TTypeKind.tkLString: InternalEnqueueAnsiString(Value);
TTypeKind.tkWString: InternalEnqueueWideString(Value);
{$ENDIF}
{$IF Defined(AUTOREFCOUNT)}
TTypeKind.tkClass: InternalEnqueueObject(Value);
{$ENDIF}
end;
end;
请注意 TTypeKind.tkDynArray
没有条目。因为这两个方法是内联的,所以内联器设法将其全部压缩为零。 Enqueue
动态数组时不执行任何操作。
回到 XE7 的美好时光,代码看起来像这样:
procedure TQueue<T>.Enqueue(const Value: T);
begin
if Count = Length(FItems) then
Grow;
FItems[FHead] := Value;
FHead := (FHead + 1) mod Length(FItems);
Inc(FCount);
Notify(Value, cnAdded);
end;
没有类型特定缺陷的范围。
我认为没有适合您的简单解决方法。也许最方便的方法是获取 XE7 TQueue
的代码并使用它来代替 XE8 和 Seattle 的损坏实现。作为记录,我已经放弃了 Embarcadero 通用集合并使用我自己的 classes.
这里的背景故事是,在 XE8 中,Embarcadero 决定解决泛型实现中的一个缺陷。每当您实例化泛型类型时,都会创建所有方法的副本。对于某些方法,会为不同的实例化生成相同的代码。
因此 TGeneric<TFoo>.DoSomething
和 TGeneric<TBar>.DoSomething
具有相同的代码是很常见的。其他语言、C++ 模板、.net 泛型等的其他编译器识别这种重复并将相同的泛型方法合并在一起。 Delphi 编译器没有。最终结果是一个比绝对必要的更大的可执行文件。
在 XE8 中,Embarcadero 决定以我认为完全错误的方式解决这个问题。他们没有攻击问题的根本原因,即编译器,而是决定更改其通用集合 classes 的实现。如果你看一下Generics.Collections
中的代码,你会发现它在XE8中已经完全re-written了。以前 XE7 和更早版本的代码是可读的,而现在 XE8 的代码非常复杂和不透明。该决定产生了以下后果:
- 复杂的代码包含很多错误。其中许多是在 XE8 发布后不久发现的,并已得到修复。您偶然发现了另一个缺陷。我们了解到的一件事是,Embarcadero 的内部测试套件没有充分使用他们的集合 classes。很明显,他们的测试不充分。
- 通过更改他们的库而不是编译器,他们已经修补了 RTL classes。通用代码膨胀的原始问题仍然存在于第三方 classes。如果 Embarcadero 在源头上解决了这个问题,那么他们不仅可以保留 XE7 中简单而正确的集合 class 代码,而且所有第三方通用代码都会受益。
在 TQueue 中存储数组时出现问题。知道我哪里出错了吗? 代码在 Delphi XE 5 中运行良好,但在 Delphi 10 Seattle 中运行不正常。
(我无法确定这是否是错误或它应该如何工作。尝试在 embarcadero 中搜索线索但失败了。)
procedure TForm1.Button1Click(Sender: TObject);
var
FData: TQueue<TBytes>;
FsData: TQueue<String>;
arr: TBytes;
begin
FData := TQueue<TBytes>.Create;
FsData := TQueue<String>.Create;
try
setlength(arr, 3);
arr[0] := 1;
arr[1] := 2;
arr[2] := 3;
FData.Enqueue(arr);
Memo1.Lines.Add('Count, array:' + IntToStr(FData.Count)); // 0?
FsData.Enqueue('asada');
Memo1.Lines.Add('Count, string:' + IntToStr(FsData.Count)); // 1
finally
FData.Free;
FsData.Free;
end;
end;
这是 XE8 中引入的缺陷。这是我能制作的最简单的复制品。
{$APPTYPE CONSOLE}
uses
System.Generics.Collections;
var
Queue: TQueue<TArray<Byte>>;
begin
Queue := TQueue<TArray<Byte>>.Create;
Queue.Enqueue(nil);
Writeln(Queue.Count);
end.
在 XE7 中输出为 1,在 XE8 和 Seattle 中输出为 0。
这已报告给 Embarcadero:RSP-13196。
Enqueue
的实现如下所示:
procedure TQueue<T>.Enqueue(const Value: T);
begin
if IsManagedType(T) then
if (SizeOf(T) = SizeOf(Pointer)) and (GetTypeKind(T) <> tkRecord) then
FQueueHelper.InternalEnqueueMRef(Value, GetTypeKind(T))
else
FQueueHelper.InternalEnqueueManaged(Value)
else
case SizeOf(T) of
1: FQueueHelper.InternalEnqueue1(Value);
2: FQueueHelper.InternalEnqueue2(Value);
4: FQueueHelper.InternalEnqueue4(Value);
8: FQueueHelper.InternalEnqueue8(Value);
else
FQueueHelper.InternalEnqueueN(Value);
end;
end;
当T
为动态数组时,选择FQueueHelper.InternalEnqueueMRef
分支。这又看起来像这样:
procedure TQueueHelper.InternalEnqueueMRef(const Value; Kind: TTypeKind);
begin
case Kind of
TTypeKind.tkUString: InternalEnqueueString(Value);
TTypeKind.tkInterface: InternalEnqueueInterface(Value);
{$IF not Defined(NEXTGEN)}
TTypeKind.tkLString: InternalEnqueueAnsiString(Value);
TTypeKind.tkWString: InternalEnqueueWideString(Value);
{$ENDIF}
{$IF Defined(AUTOREFCOUNT)}
TTypeKind.tkClass: InternalEnqueueObject(Value);
{$ENDIF}
end;
end;
请注意 TTypeKind.tkDynArray
没有条目。因为这两个方法是内联的,所以内联器设法将其全部压缩为零。 Enqueue
动态数组时不执行任何操作。
回到 XE7 的美好时光,代码看起来像这样:
procedure TQueue<T>.Enqueue(const Value: T);
begin
if Count = Length(FItems) then
Grow;
FItems[FHead] := Value;
FHead := (FHead + 1) mod Length(FItems);
Inc(FCount);
Notify(Value, cnAdded);
end;
没有类型特定缺陷的范围。
我认为没有适合您的简单解决方法。也许最方便的方法是获取 XE7 TQueue
的代码并使用它来代替 XE8 和 Seattle 的损坏实现。作为记录,我已经放弃了 Embarcadero 通用集合并使用我自己的 classes.
这里的背景故事是,在 XE8 中,Embarcadero 决定解决泛型实现中的一个缺陷。每当您实例化泛型类型时,都会创建所有方法的副本。对于某些方法,会为不同的实例化生成相同的代码。
因此 TGeneric<TFoo>.DoSomething
和 TGeneric<TBar>.DoSomething
具有相同的代码是很常见的。其他语言、C++ 模板、.net 泛型等的其他编译器识别这种重复并将相同的泛型方法合并在一起。 Delphi 编译器没有。最终结果是一个比绝对必要的更大的可执行文件。
在 XE8 中,Embarcadero 决定以我认为完全错误的方式解决这个问题。他们没有攻击问题的根本原因,即编译器,而是决定更改其通用集合 classes 的实现。如果你看一下Generics.Collections
中的代码,你会发现它在XE8中已经完全re-written了。以前 XE7 和更早版本的代码是可读的,而现在 XE8 的代码非常复杂和不透明。该决定产生了以下后果:
- 复杂的代码包含很多错误。其中许多是在 XE8 发布后不久发现的,并已得到修复。您偶然发现了另一个缺陷。我们了解到的一件事是,Embarcadero 的内部测试套件没有充分使用他们的集合 classes。很明显,他们的测试不充分。
- 通过更改他们的库而不是编译器,他们已经修补了 RTL classes。通用代码膨胀的原始问题仍然存在于第三方 classes。如果 Embarcadero 在源头上解决了这个问题,那么他们不仅可以保留 XE7 中简单而正确的集合 class 代码,而且所有第三方通用代码都会受益。