Подтвердить что ты не робот

Как правильно освобождать записи, которые содержат разные типы в Delphi сразу?

type
  TSomeRecord = Record
    field1: integer;
    field2: string;
    field3: boolean;
  End;
var
  SomeRecord: TSomeRecord;
  SomeRecAr: array of TSomeRecord;

Это самый простой пример того, что у меня есть, и поскольку я хочу повторно использовать SomeRecord (при этом некоторые поля остаются пустыми, не освобождая все поля, которые будут переноситься при повторном использовании SomeRecord, что очевидно нежелательный) Я ищу способ бесплатно освободить все поля сразу. Я начал с string[255] и использовал ZeroMemory(), что было хорошо, пока не началось утечка памяти, потому что я переключился на string. Мне все еще не хватает знаний, чтобы понять, почему, но, похоже, это связано с динамикой. Я также использую динамические массивы, поэтому я предполагаю, что попытка ZeroMemory() на любом динамическом результате приведет к утечкам. Один день потерял это. Думаю, я решил это, используя Finalize() на SomeRecord или SomeRecAr до ZeroMemory(), но я не уверен, что это правильный подход или просто я глуп.

Итак, вопрос: как освободить все сразу? существует ли какая-то одна процедура для этого, о которой я не знаю?

В другой заметке, в качестве альтернативы, я был бы открыт для предложений о том, как реализовать эти записи по-разному для начала, поэтому мне не нужно делать сложные попытки освободить материал. Я изучил создание записей с помощью New(), а затем избавился от него Dispose(), но я понятия не имею, что это означает, когда переменная после вызова Dispose() равна undefined, а не nil. Кроме того, я не знаю, какая разница между переменной определенного типа (SomeRecord: TSomeRecord) и переменной, указывающей на тип (SomeRecord: ^TSomeRecord). В настоящий момент я изучаю вышеупомянутые проблемы, если кто-то не может объяснить это быстро, это может занять некоторое время.

4b9b3361

Ответ 1

Предполагая, что у вас есть версия Delphi, которая поддерживает методы реализации в записи, вы можете очистить запись следующим образом:

type
  TSomeRecord = record
    field1: integer;
    field2: string;
    field3: boolean;
    procedure Clear;
  end;

procedure TSomeRecord.Clear;
begin
  Self := Default(TSomeRecord);
end;

Если ваш компилятор не поддерживает Default, вы можете сделать то же самое довольно просто:

procedure TSomeRecord.Clear;
const
  Default: TSomeRecord=();
begin
  Self := Default;
end;

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

type
  TSomeRecord = record
    // fields go here
    class function Empty: TSomeRecord; static;
  end;

class function TSomeRecord.Empty: TSomeRecord;
begin
  Result := Default(TSomeRecord);
end;

....

Value := TSomeRecord.Empty;

В стороне, я не могу найти ссылку на документацию для Default(TypeIdentifier). Кто-нибудь знает, где его можно найти?


Что касается второй части вашего вопроса, я не вижу причин не продолжать использовать записи и выделять их с помощью динамических массивов. Попытка управлять пожизненной жизнью гораздо более подвержена ошибкам.

Ответ 2

Не делайте мысли сложнее!

Назначение "по умолчанию" record - это просто потеря мощности процессора и памяти.

Когда a record объявляется внутри TClass, он заполняется нулем, поэтому инициализируется. Когда он выделяется в стеке, инициализируются только ссылочные подсчитанные переменные: другие виды переменных (например, целочисленные или двойные или булевы или перечисления) находятся в случайном состоянии (возможно, не равном нулю). Когда он будет выделен в куче, getmem ничего не инициализирует, allocmem заполнит все содержимое нулем, а new будет инициализировать только элементы с подсчетом ссылок (например, при инициализации стека): во всех случаях, вы должны использовать либо dispose, либо finalize+freemem, чтобы освободить выделенную кучу record.

Итак, о вашем точном вопросе, ваше собственное предположение было правильным: до reset содержимого записи после использования, никогда не используйте "fillchar" (или "zeromemory" ) без предыдущего "finalize". Вот правильный и быстрый способ:

Finalize(aRecord);
FillChar(aRecord,sizeof(aRecord),0);

Опять же, это будет быстрее, чем присвоение записи по умолчанию. И во всех случаях, если вы используете finalize, даже несколько раз, он не будет утечки памяти - 100% гарантия возврата денег!

Изменить: просмотрев код, сгенерированный aRecord := default(TRecordType), код хорошо оптимизирован: на самом деле это finalize + связка stosd для эмуляции fillchar. Поэтому, даже если синтаксис является копией/присваиванием (:=), он не реализован как копия/присваивание. Моя ошибка здесь.

Но мне все еще не нравится тот факт, что нужно использовать :=, где Embarcadero должен лучше использовать метод record, такой как aRecord.Clear как синтаксис, как Динамические массивы DelphiWebScript. Фактически, этот синтаксис := тот же самый, который используется С#. Похоже, если Embacardero просто имитирует синтаксис С# всюду, не обнаружив, что это странно. В чем смысл, если Delphi является всего лишь последователем, а не реализует думает "своим путем" ? Люди всегда будут предпочитать оригинальный С# своему предку (у Дельфи есть тот же отец).

Ответ 3

Самое простое решение, о котором я думаю, будет:

const
  EmptySomeRecord: TSomeRecord = ();
begin
  SomeRecord := EmptySomeRecord;

Но для решения всех оставшихся частей вашего вопроса выполните следующие определения:

type
  PSomeRecord = ^TSomeRecord;
  TSomeRecord = record
    Field1: Integer;
    Field2: String;
    Field3: Boolean;
  end;
  TSomeRecords = array of TSomeRecord;
  PSomeRecordList = ^TSomeRecordList;
  TSomeRecordList = array[0..MaxListSize] of TSomeRecord;    
const
  EmptySomeRecord: TSomeRecord = ();
  Count = 10;    
var
  SomeRecord: TSomeRecord;
  SomeRecords: TSomeRecords;
  I: Integer;
  P: PSomeRecord;
  List: PSomeRecordList;

procedure ClearSomeRecord(var ASomeRecord: TSomeRecord);
begin
  ASomeRecord.Field1 := 0;
  ASomeRecord.Field2 := '';
  ASomeRecord.Field3 := False;
end;

function NewSomeRecord: PSomeRecord;
begin
  New(Result);
  Result^.Field1 := 0;
  Result^.Field2 := '';
  Result^.Field3 := False;
end;

И вот несколько примеров о том, как работать с ними:

begin
  // Clearing a typed variable (1):
  SomeRecord := EmptySomeRecord;

  // Clearing a typed variable (2):
  ClearSomeRecord(SomeRecord);

  // Initializing and clearing a typed array variabele:
  SetLength(SomeRecords, Count);

  // Creating a pointer variable:
  New(P);

  // Clearing a pointer variable:
  P^.Field1 := 0;
  P^.Field2 := '';
  P^.Field3 := False;

  // Creating and clearing a pointer variable:
  P := NewSomeRecord;

  // Releasing a pointer variable:
  Dispose(P);

  // Creating a pointer array variable:
  ReallocMem(List, Count * SizeOf(TSomeRecord));

  // Clearing a pointer array variable:
  for I := 0 to Count - 1 do
  begin
    Pointer(List^[I].Field2) := nil;
    List^[I].Field1 := 0;
    List^[I].Field2 := '';
    List^[I].Field3 := False;
  end;

  // Releasing a pointer array variable:
  Finalize(List^[0], Count);

Выберите и/или объедините, как вам нравится.

Ответ 4

С SomeRecord: TSomeRecord, SomeRecord будет экземпляром/переменной типа TSomeRecord. С SomeRecord: ^TSomeRecord, SomeRecord будет указателем на экземпляр или переменную типа TSomeRecord. В последнем случае SomeRecord будет введенным указателем. Если ваше приложение переносит множество данных между подпрограммами или взаимодействует с внешним API, рекомендуется вводить указатель на указатель.

new() и dispose() используются только с типизированными указателями. С помощью типизированных указателей компилятор не имеет управления /knowlegde памяти, используемой вашим приложением с такими варами. Это вам, чтобы освободить память, используемую типизированными указателями.

С другой стороны, когда вы используете обычные переменные, в зависимости от использования и объявления, компилятор освободит память, используемую ими, когда он считает, что они больше не нужны. Например:

function SomeStaff();
var
    NativeVariable: TSomeRecord;
    TypedPointer: ^TSomeRecord;
begin
    NaviveVariable.Field1 := 'Hello World';

    // With typed pointers, we need to manually
    // create the variable before we can use it.
    new(TypedPointer);
    TypedPointer^.Field1 := 'Hello Word';

    // Do your stuff here ...

    // ... at end, we need to manually "free"
    // the typed pointer variable. Field1 within
    // TSomerecord is also released
    Dispose(TypedPointer);

    // You don't need to do the above for NativeVariable
    // as the compiler will free it after this function
    // ends. This apply also for native arrays of TSomeRecord.
end;

В приведенном выше примере переменная NativeVariable используется только в функции SomeStaff, поэтому компилятор автоматически освобождает ее, когда функция заканчивается. Это приложение для большинства большинства переменных, включая массивы и записи "поля". Объекты обрабатываются по-разному, но для другого сообщения.