Delphi TQueue багги? Использование tqueue возвращает nil с dequeue

Я не понимаю, почему этот простой код не удалось? Я на Delphi Tokyo release 2.

{$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.

это ошибка?

Примечание: код работает правильно на XE4, но не работает и на Берлине.

2 ответов


Это действительно ошибка. Код работает правильно в XE7, но не XE8. В XE8 выход равен 0 для обеих попытках.

конечно, общие коллекции XE8 были очень глючными, и последующие выпуски исправили многие дефекты. Очевидно, не все исправлено.

в XE8 Embarcadero предпринята попытка решить проблему общего раздувания, вызванного недостатками в их модели компиляции/ссылки. К сожалению, вместо того, чтобы решать проблему в корне, они предпочли устраните проблему в коде библиотеки для универсальных коллекций. При этом они полностью сломали многие из общих классов коллекции, доказав, что их модульное тестирование было слабым. И, конечно, решая проблему таким образом, они не смогли решить проблему общего раздувания для классов, отличных от классов в общих коллекциях. В общем, печальная история, которая, казалось бы, еще не закончилась.

Локи только что отправил отчет об ошибке: RSP-20400.

обратите внимание, что этот отчет об ошибке неверен, потому что (по крайней мере, по словам Стефана Гленке) ошибка была исправлена в Токио 10.2.3. Таким образом, обновление до 10.2.3 должно быть самым простым способом решения проблемы.

возможно, этот отчет об ошибке более уместен:RSP-17728.

написать общую очередь даже не сложно. Вот один, который, как известно, работает:

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;

чтобы добавить к ответу Дэвида, ошибка находится в Enqueue метод. Верхняя ветвь должна обрабатывать все подсчитанные ссылки управляемых типов.

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

но здесь мы видим, что динамические массивы явно отсутствует в 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;

это так вопиюще, на самом деле, что компилятор фактически не производит код для Enqueue при компиляции (кроме преамбулы), так как бесполезность упражнения может быть определена из типы во время компиляции.

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

поэтому ожидается, что эта ошибка повлияет на TQueue<T> на T быть любым типом динамического массива.