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.