2016-01-13 22 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