Как правильно освобождать записи, которые содержат разные типы в 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
). В настоящий момент я изучаю вышеупомянутые проблемы, если кто-то не может объяснить это быстро, это может занять некоторое время.
Ответы
Ответ 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
, поэтому компилятор автоматически освобождает ее, когда функция заканчивается. Это приложение для большинства большинства переменных, включая массивы и записи "поля". Объекты обрабатываются по-разному, но для другого сообщения.