2016-01-13 72 views
12

将问题存储在TQueue中时出现问题。任何想法我错了? 代码在Delphi XE 5中正常工作,但在Delphi 10 Seattle中无法正常工作。将数组存储在TQueue中?

(我不能决定,如果这是一个错误,或者它应该如何工作。试图寻找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

此外,我们不需要另一个不兼容的字节数组类型。使用'TBytes'。对于“Byte”以外的元素类型,更一般地使用'TArray '。 –

+0

我同意。原始数组是TidBytes(Indy) – Hans

+1

什么不起作用? –

回答

20

这是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和西雅图的破坏实现。为了记录,我放弃了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的简单而正确的集合类代码,而且所有第三个通用代码都会受益。
+1

感谢您整理出来 – Hans