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
быть любым типом динамического массива.