可以在TQueue中存储数组吗?

时间:2016-01-13 08:17:31

标签: delphi delphi-10-seattle

在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;

1 个答案:

答案 0 :(得分:21)

这是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和西雅图的输出为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通用集合并使用我自己的类。

这里的背后故事是,在XE8中,Embarcadero决定解决其仿制药实施的不足。每当您实例化泛型类型时,都会创建所有方法的副本。对于某些方法,为不同的实例化生成相同的代码。

因此TGeneric<TFoo>.DoSomethingTGeneric<TBar>.DoSomething具有相同的代码是很常见的。其他语言的其他编译器,C ++模板,.net泛型等,识别这种重复并将相同的通用方法合并在一起。 Delphi编译器没有。最终结果是一个比严格必要的更大的可执行文件。

在XE8中,Embarcadero决定以我认为完全错误的方式解决这个问题。他们没有攻击问题的根本原因,而是决定改变其泛型集合类的实现。如果查看Generics.Collections中的代码,您会发现它已在XE8中完全重写。以前XE7及更早版本的代码是可读的,从XE8开始,它现在非常复杂和不透明。该决定产生了以下后果:

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