2016-01-13 2 views
12

Ошибка при хранении массива в TQueue. Любая идея, где я ошибаюсь? Код отлично работает в Delphi XE 5, но не в Delphi 10 Seattle.Возможно сохранение массива в TQueue?

(я не могу решить, если это ошибка или как она должна работать. Пытался искать Причал для ключей, но не удалось.)

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

Кроме того, нам не нужен еще один несовместимый тип массива байтов. Используйте 'TBytes'. В более общем случае используйте 'TArray ' для типов элементов, отличных от 'Byte'. –

+0

Согласен. Начальный массив TidBytes (Indy) – Hans

+1

Что не работает? –

ответ

20

Это дефект вводится в X Е8. Вот простейшее воспроизведение, которое я могу произвести.

{$APPTYPE CONSOLE} 

uses 
    System.Generics.Collections; 

var 
    Queue: TQueue<TArray<Byte>>; 

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

Выходной сигнал 1 в XE7 и 0 в XE8 и Сиэтле.

Об этом сообщается Embarcadero: RSP-13196.


Реализация Enqueue выглядит следующим образом:

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; 

Когда T динамический массив, выбирается FQueueHelper.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; 

Обратите внимание, что нет записи для TTypeKind.tkDynArray. Поскольку эти два метода встроены, inliner удается сжать все до нуля. Никакие действия не выполняются, если вы используете Enqueue динамический массив.

В старые добрые времена XE7 код выглядел так:

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; 

Нет возможности для конкретных дефектов типа там.


Я не думаю, что для вас есть простой способ обхода. Возможно, наиболее целесообразным способом является принятие кода для XE7 TQueue и использование этого вместо сломанной реализации от XE8 и Seattle. Для записи я отказался от общих коллекций Embarcadero и использовал свои собственные классы.


Предыстория здесь является то, что в X Е8, Embarcadero решил обратиться к недостатку в их реализации дженериков. Всякий раз, когда вы создаете типичный тип, создаются копии всех методов. Для некоторых методов идентичный код генерируется для разных экземпляров.

Так что для TGeneric<TFoo>.DoSomething и TGeneric<TBar>.DoSomething это идентичный код. Другие компиляторы для других языков, шаблоны C++, .net generics и т. Д., Распознают это дублирование и объединяют идентичные общие методы. Компилятор Delphi этого не делает. Конечный результат - это более сложный исполняемый файл, чем это необходимо.

В XE8 Эмбаркадеро решил заняться этим в том, что я считаю совершенно неправильным. Вместо того, чтобы атаковать первопричину проблемы, компилятор решил изменить реализацию своих общих классов коллекций. Если вы посмотрите на код в Generics.Collections, вы увидите, что он полностью переписан в XE8. Если ранее код из XE7 и ранее был доступен для чтения, то из XE8 он теперь чрезвычайно сложный и непрозрачный.Это решение имеет следующие последствия:

  1. Комплексный код содержит много ошибок. Многие из них были обнаружены вскоре после того, как XE8 был выпущен и исправлен. Вы наткнулись на другой недостаток. Одна вещь, которую мы узнали, заключается в том, что внутренний набор тестов Embarcadero недостаточно реализует свои классы коллекций. Очевидно, что их тесты неадекватны.
  2. Изменяя свою библиотеку, а не компилятор, они закрепили классы RTL. Оригинальная проблема с родовым раздуванием кода остается для сторонних классов. Если бы Embarcadero исправил проблему у источника, то они не только сохранили бы простой и правильный код класса коллекции из XE7, но и получили бы все третий общий код.
+1

Спасибо, что разобрали его – Hans

Смежные вопросы