tokyo studio software full embarcadero compiladores delphi delphi-10.2-tokyo

studio - ¿Delphi TQueue Buggy? Usando TQueue<Tbytes> devuelve nil con el dequeue



rad embarcadero delphi (2)

Esto es de hecho un error. El código funciona correctamente en XE7, pero no XE8. En XE8 la salida es 0 para ambos intentos.

Ciertamente, las colecciones genéricas de XE8 tenían muchos errores y las versiones posteriores solucionaron muchos de los defectos. Claramente no todos han sido arreglados.

En XE8, Embarcadero intentó abordar el problema de la expansión de genéricos causada por debilidades en su modelo de compilación / enlace. Desafortunadamente, en lugar de abordar el problema desde la raíz, optaron por resolver el problema en el código de la biblioteca para colecciones genéricas. Al hacerlo, rompieron por completo muchas de las clases de colección genéricas, lo que demuestra que sus pruebas de unidad fueron débiles. Y, por supuesto, al abordar el problema de esta manera, no lograron resolver el problema de la acumulación de genéricos para otras clases que no sean las de las colecciones genéricas. En definitiva, una triste historia que aparentemente aún no ha terminado.

loki acaba de enviar un informe de error: RSP-20400 .

Tenga en cuenta que este informe de error es incorrecto porque (al menos según Stefan Glienke) el error se ha corregido en Tokio 10.2.3. Por lo tanto, actualizar a 10.2.3 debería ser la forma más sencilla de resolver el problema.

Quizás este informe de error sea más apropiado: RSP-17728 .

Escribir una cola genérica ni siquiera es difícil. Aquí hay uno que se sabe que funciona:

type TQueue<T> = class private FItems: TArray<T>; FCount: Integer; FFront: Integer; private function Extract(Index: Integer): T; inline; function GetBack: Integer; inline; property Back: Integer read GetBack; property Front: Integer read FFront; procedure Grow; procedure RetreatFront; inline; public property Count: Integer read FCount; procedure Clear; procedure Enqueue(const Value: T); function Dequeue: T; function Peek: T; public type TEnumerator = record private FCollection: TQueue<T>; FCount: Integer; FCapacity: Integer; FIndex: Integer; FStartIndex: Integer; public class function New(Collection: TQueue<T>): TEnumerator; static; function GetCurrent: T; property Current: T read GetCurrent; function MoveNext: Boolean; end; public function GetEnumerator: TEnumerator; end; function GrownCapacity(OldCapacity: Integer): Integer; var Delta: Integer; begin if OldCapacity>64 then begin Delta := OldCapacity div 4 end else if OldCapacity>8 then begin Delta := 16 end else begin Delta := 4; end; Result := OldCapacity + Delta; end; { TQueue<T> } function TQueue<T>.Extract(Index: Integer): T; begin Result := FItems[Index]; if IsManagedType(T) then begin Finalize(FItems[Index]); end; end; function TQueue<T>.GetBack: Integer; begin Result := Front + Count - 1; if Result>high(FItems) then begin dec(Result, Length(FItems)); end; end; procedure TQueue<T>.Grow; var Index: Integer; Value: T; Capacity: Integer; NewItems: TArray<T>; begin Capacity := Length(FItems); if Count=Capacity then begin SetLength(NewItems, GrownCapacity(Capacity)); Index := 0; for Value in Self do begin NewItems[Index] := Value; inc(Index); end; FItems := NewItems; FFront := 0; end; end; procedure TQueue<T>.RetreatFront; begin inc(FFront); if FFront=Length(FItems) then begin FFront := 0; end; end; procedure TQueue<T>.Clear; begin FItems := nil; FCount := 0; end; procedure TQueue<T>.Enqueue(const Value: T); begin Grow; inc(FCount); FItems[Back] := Value; end; function TQueue<T>.Dequeue: T; var Index: Integer; begin Assert(Count>0); Result := Extract(Front); RetreatFront; dec(FCount); end; function TQueue<T>.Peek: T; begin Assert(Count>0); Result := FItems[Front]; end; function TQueue<T>.GetEnumerator: TEnumerator; begin Result := TEnumerator.New(Self); end; { TQueue<T>.TEnumerator } class function TQueue<T>.TEnumerator.New(Collection: TQueue<T>): TEnumerator; begin Result.FCollection := Collection; Result.FCount := Collection.Count; Result.FCapacity := Length(Collection.FItems); Result.FIndex := -1; Result.FStartIndex := Collection.Front; end; function TQueue<T>.TEnumerator.GetCurrent: T; var ActualIndex: Integer; begin ActualIndex := (FStartIndex + FIndex) mod FCapacity; Result := FCollection.FItems[ActualIndex]; end; function TQueue<T>.TEnumerator.MoveNext: Boolean; begin inc(FIndex); Result := FIndex<FCount; end;

No entiendo por qué este código tan simple falló? Estoy en la versión 2 de Delphi Tokyo.

{$APPTYPE CONSOLE} uses System.SysUtils, System.Generics.Collections; procedure Main; var aQueue: TQueue<TBytes>; aBytes: TBytes; begin aQueue := TQueue<TBytes>.create; aBytes := TEncoding.UTF8.GetBytes(''abcd''); aQueue.Enqueue(aBytes); aBytes := aQueue.Dequeue; Writeln(Length(aBytes)); // outputs 4 as expected aBytes := TEncoding.UTF8.GetBytes(''abcd''); aQueue.Enqueue(aBytes); aBytes := aQueue.Dequeue; Writeln(Length(aBytes)); // outputs 0 end; begin Main; Readln; end.

¿Es esto un error?

NOTA: El código funciona correctamente en XE4, pero también falla en Berlín.


Para agregar a la respuesta de David, el error está en el método Enqueue . La rama superior debe manejar todos los tipos administrados contabilizados de referencia.

if IsManagedType(T) then if (SizeOf(T) = SizeOf(Pointer)) and (GetTypeKind(T) <> tkRecord) then FQueueHelper.InternalEnqueueMRef(Value, GetTypeKind(T)) else FQueueHelper.InternalEnqueueManaged(Value) else

Pero aquí vemos que las matrices dinámicas faltan de manera visible en InternalEnqueueMref , que falla sin hacer nada:

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;

De hecho, es tan grave que el compilador en realidad no produce código para Enqueue cuando se compila (aparte del preámbulo), ya que la inutilidad del ejercicio se puede determinar a partir de los tipos en tiempo de compilación.

Project1.dpr.15: aQueue.Enqueue(aBytes); 0043E19E 8B45F8 mov eax,[ebp-$08] 0043E1A1 8945F4 mov [ebp-$0c],eax 0043E1A4 8B45FC mov eax,[ebp-$04] 0043E1A7 83C008 add eax,$08 0043E1AA 8945F0 mov [ebp-$10],eax Project1.dpr.16: aBytes := aQueue.Dequeue; 0043E1AD 8D45EC lea eax,[ebp-$14]

Por lo tanto, se espera que este error afecte a TQueue<T> ya que T es cualquier tipo de matriz dinámica.