可以在 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>.DoSomethingTGeneric<TBar>.DoSomething 具有相同的代码是很常见的。其他语言、C++ 模板、.net 泛型等的其他编译器识别这种重复并将相同的泛型方法合并在一起。 Delphi 编译器没有。最终结果是一个比绝对必要的更大的可执行文件。

在 XE8 中,Embarcadero 决定以我认为完全错误的方式解决这个问题。他们没有攻击问题的根本原因,即编译器,而是决定更改其通用集合 classes 的实现。如果你看一下Generics.Collections中的代码,你会发现它在XE8中已经完全re-written了。以前 XE7 和更早版本的代码是可读的,而现在 XE8 的代码非常复杂和不透明。该决定产生了以下后果:

  1. 复杂的代码包含很多错误。其中许多是在 XE8 发布后不久发现的,并已得到修复。您偶然发现了另一个缺陷。我们了解到的一件事是,Embarcadero 的内部测试套件没有充分使用他们的集合 classes。很明显,他们的测试不充分。
  2. 通过更改他们的库而不是编译器,他们已经修补了 RTL classes。通用代码膨胀的原始问题仍然存在于第三方 classes。如果 Embarcadero 在源头上解决了这个问题,那么他们不仅可以保留 XE7 中简单而正确的集合 class 代码,而且所有第三方通用代码都会受益。