2016-01-13 16 views
12

Po problemie przechowywania tablicy w TQueue. Jakiś pomysł, gdzie popełniam błąd? Kod działa dobrze w Delphi XE 5, ale nie w Delphi 10 w Seattle.Czy można zapisać tablicę w TQueue?

(nie mogę zdecydować, czy jest to błąd i jak to powinno działać. Próbowałem wyszukiwanie Embarcadero wskazówek, ale nie powiodło się.)

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

Poza tym nie potrzebujemy kolejnego niekompatybilnego typu tablicy bajtowej. Użyj 'TBytes'. Bardziej ogólnie użyj 'TArray ' dla typów elementów innych niż 'Byte'. –

+0

Zgadzam się. Tablica początkowa to TidBytes (Indy) – Hans

+1

Co nie działa? –

Odpowiedz

20

Jest to wada wprowadzony w XE8. Oto najprostsza reprodukcja, którą mogę wyprodukować.

{$APPTYPE CONSOLE} 

uses 
    System.Generics.Collections; 

var 
    Queue: TQueue<TArray<Byte>>; 

begin 
    Queue := TQueue<TArray<Byte>>.Create; 
    Queue.Enqueue(nil); 
    Writeln(Queue.Count); 
end. 

Wyjście to 1 w XE7 i 0 w XE8 i Seattle.

Zostało to już zgłoszone do Embarcadero: RSP-13196.


Realizacja Enqueue wygląda następująco:

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; 

Kiedy T to dynamiczna tablica The FQueueHelper.InternalEnqueueMRef oddział jest wybrany. To z kolei wygląda następująco:

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; 

Zauważ, że nie ma wpisu dla TTypeKind.tkDynArray. Ponieważ te dwie metody są wbudowane, inlinerowi udaje się skompresować wszystko do zera. Nie wykonujesz żadnej akcji, gdy jesteś dynamiczną tablicą.

Powrót do starych, dobrych czasów XE7 kod wyglądał następująco:

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; 

No zakres wad specyficzny rodzaj tam.


Nie sądzę, że istnieje łatwy sposób obejścia problemu. Być może najbardziej sensownym sposobem postępowania jest pobranie kodu XE7 TQueue i użycie go zamiast uszkodzonej implementacji z XE8 i Seattle. Dla przypomnienia zrezygnowałem z generycznych kolekcji Embarcadero i wykorzystałem własne klasy.


Opowieść o tym, że w XE8, Embarcadero zdecydowało się rozwiązać problem niedoboru w ich stosowaniu leków generycznych. Za każdym razem, gdy tworzysz typ ogólny, tworzone są kopie wszystkich metod. W przypadku niektórych metod generowany jest identyczny kod dla różnych wystąpień.

Jest więc dość powszechne, aby TGeneric<TFoo>.DoSomething i TGeneric<TBar>.DoSomething miały identyczny kod. Inne kompilatory dla innych języków, szablony C++, .NET generics itp. Rozpoznają to duplikowanie i łączą ze sobą identyczne generyczne metody. Kompilator Delphi tego nie robi. Końcowym rezultatem jest większy plik wykonywalny, niż jest to absolutnie konieczne.

W XE8 Embarcadero zdecydował się rozwiązać ten problem w sposób, który uważam za całkowicie niewłaściwy. Zamiast atakować główną przyczynę problemu, kompilator, postanowili zmienić implementację swoich ogólnych klas kolekcji. Jeśli spojrzysz na kod w Generics.Collections, zobaczysz, że został całkowicie przepisany w XE8. Tam, gdzie wcześniej kod z XE7 i wcześniejszych był czytelny, od XE8 jest teraz niezwykle złożony i nieprzejrzysty.Decyzja ta miała następujące konsekwencje:

  1. Kod złożony zawierał wiele błędów. Wiele z nich znaleziono wkrótce po wydaniu XE8 i naprawiono. Natknąłeś się na kolejną wadę. Jedną z rzeczy, których się nauczyliśmy, jest to, że wewnętrzny zestaw testów Embarcadero nie wykonuje wystarczająco swoich klas zbierania danych. Oczywiste jest, że ich testy są niewystarczające.
  2. Zmieniając bibliotekę, a nie kompilator, poprawili klasy RTL. Pierwotny problem z ogólnym nadpisaniem kodu pozostaje dla klas stron trzecich. Gdyby Embarcadero naprawił problem u źródła, to nie tylko mogliby zachować prosty i prawidłowy kod kolekcji z XE7, ale skorzystałby z tego cały trzeci kod generyczny.
+1

Dzięki za uporządkowanie tego – Hans

Powiązane problemy