【问题标题】:Store array in TQueue possible?可以在 TQueue 中存储数组吗?
【发布时间】:2016-04-18 02:53:57
【问题描述】:

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

【问题讨论】:

  • 除此之外,我们不需要另一个不兼容的字节数组类型。使用TBytes。对于Byte以外的元素类型,更普遍地使用TArray&lt;T&gt;
  • 我同意。原始数组是 TidBytes (Indy)
  • @RudyVelthuis 代码中的 cmets 解释了问题所在。我的答案中的重现更清晰。
  • 是的,后来我看到了。确实看起来有点马车。

标签: delphi delphi-10-seattle


【解决方案1】:

这是 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 和西雅图的损坏实现。郑重声明,我已经放弃了 Embarcadero 泛型集合并使用我自己的类。


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

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

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

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

【讨论】:

  • 感谢整理
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-01-11
  • 1970-01-01
  • 1970-01-01
  • 2010-12-02
  • 2011-04-05
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多