2016-01-13 13 views
12

Problema durante la memorizzazione di un array in un TQueue. Qualche idea su dove vado storto? Il codice funziona bene in Delphi XE 5 ma non in Delphi 10 Seattle.È possibile archiviare array in TQueue?

(Non riesco a decidere se questo è un bug o come dovrebbe funzionare. Provato a cercare embarcadero di indizi, ma non è riuscito.)

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

A parte questo, non è necessario un altro tipo di array di byte incompatibile. Usa 'TBytes'. Più in generale usa 'TArray ' per tipi di elementi diversi da 'Byte'. –

+0

Sono d'accordo. L'array di origine è TidBytes (Indy) – Hans

+1

Che cosa non funziona? –

risposta

20

Questo è un difetto introdotto nel XE8. Ecco la riproduzione più semplice che posso produrre.

{$APPTYPE CONSOLE} 

uses 
    System.Generics.Collections; 

var 
    Queue: TQueue<TArray<Byte>>; 

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

L'uscita è 1 in XE7 e 0 in XE8 e Seattle.

Questo è già stato segnalato a Embarcadero: RSP-13196.


L'attuazione Enqueue aspetto:

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; 

Quando T è un array dinamico, il ramo FQueueHelper.InternalEnqueueMRef è scelto. Questo a sua volta si presenta così:

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; 

Si noti che non v'è alcuna voce per TTypeKind.tkDynArray. Poiché questi due metodi sono in linea, l'inliner riesce a comprimere tutto fino al nulla. Nessuna azione viene eseguita quando si imposta un array dinamico Enqueue.

Torna nei bei vecchi tempi di XE7 il codice si presentava così:

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; 

Nessuna possibilità di difetti specifici di tipo lì.


Non penso che ci sia una soluzione facile per te. Forse il modo più conveniente per procedere è prendere il codice per XE7 TQueue e utilizzarlo al posto dell'implementazione non funzionante da XE8 e Seattle. Per la cronaca, ho rinunciato alle collezioni generiche di Embarcadero e uso le mie lezioni.


La parte posteriore storia qui è che in XE8, Embarcadero ha deciso di affrontare una carenza nella loro attuazione dei farmaci generici. Ogni volta che istanziate un tipo generico, vengono create copie di tutti i metodi. Per alcuni metodi, viene generato un codice identico per diverse istanze.

Quindi è abbastanza comune per TGeneric<TFoo>.DoSomething e TGeneric<TBar>.DoSomething avere il codice identico. Altri compilatori per altre lingue, modelli C++, generici .net, ecc. Riconoscono questa duplicazione e uniscono insieme metodi identici generici. Il compilatore Delphi no. Il risultato finale è un eseguibile più grande di quello strettamente necessario.

In XE8 Embarcadero ha deciso di affrontare questo in quello che considero assolutamente sbagliato. Invece di attaccare la causa principale del problema, il compilatore, decisero di cambiare l'implementazione delle loro classi di raccolta generiche. Se si guarda il codice in Generics.Collections, si vedrà che è stato completamente riscritto in XE8. Laddove prima era leggibile il codice da XE7 e precedenti, da XE8 ora è estremamente complesso e opaco.Questa decisione ha avuto le seguenti conseguenze:

  1. Il codice complesso conteneva molti errori. Molti di questi sono stati trovati poco dopo che XE8 è stato rilasciato e risolto. Sei incappato in un altro difetto. Una cosa che abbiamo imparato è che la suite di test interna di Embarcadero non esercita le loro classi di raccolta sufficientemente. È palesemente chiaro che i loro test sono inadeguati.
  2. Modificando la libreria piuttosto che il compilatore, hanno corretto le classi RTL. Il problema originale con il bloat generico del codice rimane per le classi di terze parti. Se Embarcadero avesse risolto il problema all'origine, non solo avrebbero potuto mantenere il codice della classe di raccolta semplice e corretto di XE7, ma tutto il terzo codice generico ne avrebbe beneficiato.
+1

Grazie per averlo risolto – Hans

Problemi correlati