![]() |
|
|
|||||||
| Регистрация | << Правила форума >> | FAQ | Пользователи | Календарь | Поиск | Сообщения за сегодня | Все разделы прочитаны |
![]() |
|
|
Опции темы | Поиск в этой теме | Опции просмотра |
|
#1
|
||||
|
||||
|
Про массивы написано много, но одного момента я уяснить так и не смог:
если увеличивать размер массива объектов, под дополнительные объекты просто будет выделена память, сами новые объекты надо создать самостоятельно. Вроде все понятно. А вот что происходит с объектом когда он оказывается за пределами массива при уменьшении размера? По логике - вроде бы система должна "забыть" про этот объект и считать выделенную под него память свободной. Как это вообще работает, нужно ли уничтожать объект? А ну да, объект создается как то так: Код:
function TTask.IncProductList:TProduct; begin SetLength(FProductList, Length(FProductList)+1); FProductList[High(FProductList)]:= TProduct.Create; Result:= FProductList[High(FProductList)]; end; Последний раз редактировалось Помидоркин, 21.07.2022 в 08:47. |
|
#2
|
|||
|
|||
|
В данном случае, массива объектов, на самом деле в массиве выделяется только память под указатели на объекты. Память под сами объекты выделяется отдельно при создании самого объекта. Таким образом, если ты уменьшишь массив не удалив предварительно сам объект, то получишь мемори лик (Memory leak).
Условно говоря, одна ячейка массива (берем 32-битное приложение, для 64-битного тоже самое, просто размеры больше) занимает 4 байта. Пусть сам объект занимает 1кб (поля + все нужое служебное типа таблицы динамических методов и т.д.). Тогда свм массив из 100 ячеек будет занимать 400 байт (ну там есть еще несколько байт на служебную инфу, но это фикс. размер для массива). Плюс еще отдельно выделено 100 кб для самих объектов. При уменьшении размера массива на 10 элементов у тебя изменится только память, занимаемая массивом - теперь станет 360 байт. А вот если ты не вызвал деструкторы для самих объектов, то под объекты так и останется выделенно 100 кб. И при этом ты потеряешь ссылки на те 10 объектов, т.е. после этого эти 10 кб вообзе зависнут. Все вышесказанное относится к классическим объектам, т.е. классам, унаследованным от TObject. Для COM объектов ситуация немного другая - там есть механизм счетчика ссылок и как только для инстанса он сравнивается с 0, то происходит автоматическое удаление объекта. Вроде что-то писали про подобную реализацию и для классических объектов, но, по крайней мере в 10.2, такого пока нет. Надеюсь, объяснил достаточно понятно... ЗЫ. Для хранения коллекции объектов тогда лучше использовать TObjectList. Там можно сделать так (при вызове конструктора списка надо в параметрах указать, что он является "владельцем" помещенных в него объектов), что при удалении элемента будет автоматически вызван деструктор этого объекта. Да и удалять тогда можно из произваольного места списка, а не только с конца, как в случае массива. Последний раз редактировалось lmikle, 21.07.2022 в 17:36. |
| Этот пользователь сказал Спасибо lmikle за это полезное сообщение: | ||
Помидоркин (25.07.2022)
| ||
|
#3
|
||||
|
||||
|
Попробую не плодить тем, спрошу здесь
Цитата:
А вопрос возник следующий: как именно следует уничтожать объекты? Нашел вот это Код:
unit System;
...
destructor TObject.Destroy;
begin
end;
procedure TObject.Free;
begin
// under ARC, this method isn't actually called since the compiler translates
// the call to be a mere nil assignment to the instance variable, which then calls _InstClear
if Self <> nil then
Destroy;
end;В одном месте прочитал туманное объяснение что TObject - прародитель всех объектов и поэтому его деструктор вызывается как-то хитро и он все-таки что-то делает, но подробностей я так и не понял. И в комменте написано: "в ARC этот метод фактически не вызывается, поскольку компилятор преобразует вызов в простое присваивание переменной экземпляра nil, которое затем вызывает _Its Clear" Ну т.е. это и все, можно со спокойным сердцем считать что память из-под объекта освободилась? И еще: Код:
TPartForProduct = class(TObject)
private
FIndx: Word;
FCheckID: string;
FCount: Byte;
public
property Indx: Word read FIndx write FIndx;
property CheckID: string read FCheckID write FCheckID;
property Count: Byte read FCount write FCount;
end;
TPartList = array of TPartForProduct;
TProduct = class(TObject)
private
FName: string;
FAlias: string;
FPartList: TPartList;
function GetPartListCount: Byte;
procedure SetPartListCount(const Value: Byte);
public
function IncPartSet:TPartForProduct;
property Name: string read FName write FName;
property Alias: string read FAlias write FAlias;
property PartList: TPartList read FPartList write FPartList;
property PartListCount: Byte read GetPartListCount write SetPartListCount;
end;2-На данный момент в объектах только строки и числа, а как быть со всякими StringList или JPEGImage? Их тоже удалять в ручную? |
|
#4
|
|||
|
|||
|
Вообще, методы, объявленные в коде как constructor и destructor, таковыми на самом деле не являются. Правдя тут надо отметить, что доступа к реальным конструктору и деструктору в Дельфи просто нет (не считаем различные хаки). Поэтому данные методы и используются для реализации выделения памяти под атрибуты классов (имеется в виду сложные атрибуты, являющиеся так же объектами классов, память под простые типы выделяется именно реальным конструктором, но вызов его скрыт от программиста). Т.е. по сути constructor и destructor являются инициализатором и деинициализатором по своей сути. Это для начала.
Теперь по повду "излишней функциональности". Во первых, ее всегда можно скрыть: Код:
type
TMyObjectWithItems = class
private
FItems : TObjectList;
function GetItem(Index : Integer) : TAnotherObject;
function GetCount : Integer;
...
public
property Items[Index : Integer] : TAnotherObject read GetItem;
property Count : Integer read GetCount;
end;
...
function TMyObjectWithItems.GetItem(Index : Integer) : TAnotherObject;
begin
Result := FItems[Index] As TAnotherObject;
end;
function TMyObjectWithItems.GetCount : Integer;
begin
Result := FItems.Count;
end;Таким образом ты прячешь "излишнюю функциональность", но можешь ею пользоваться, если надо. Теперь ответы на последние 2 вопроса. 1. Да, надо. Для проверки, если используешь одну из последних версий Дельфи, можешь включить отчет об утечках памяти. При закрытии приложения тебе менеджер памяти скажет, если есть утечки. 2. Да, их тоже надо удалять в деструкторе. В принципе, при правльном дизайне, это по одной строчке в конструкторе и деструкторе (создать и, соответсвенно, удалить). |
| Этот пользователь сказал Спасибо lmikle за это полезное сообщение: | ||
Помидоркин (24.08.2022)
| ||
|
#5
|
|||
|
|||
|
Для примера, вот моделька из одного из моих проектов. Она простая, там всего 2 уровня - сама модель и ее итемы.
Код:
unit Model;
interface
uses
Classes, SysUtils, Contnrs, DateUtils, Generics.Collections, Forms, ActiveX, ModelBase,
System.Variants, Dialogs;
const
sigFileMark : String = 'bhpvMonitor';
sigFileVersion : Integer = 1;
type
TMonitorItem = class(TModelItemBase)
private
FGroupName : String;
FGUID : TGUID;
FUrl : String;
FItemName : String;
FComments : String;
FInitPrice : Currency;
FInitDate : TDateTime;
FWantedPrice : Currency;
FLastPrice : Currency;
FLastDate : TDateTime;
FOnHold : Boolean;
FFlagged : Boolean;
protected
procedure CreateInitFields;
public
constructor Create(AUrl : String);
constructor Load(AStream : TStream; ADataVer : Integer);
procedure Assign(AItem : TMonitorItem);
procedure SaveToStream(AStream : TStream); override;
procedure LoadFromStream(AStream : TStream); override;
procedure LoadFromStreamVer(AStream : TStream; ADataVer : Integer); virtual;
procedure UpdateData;
property GUID : TGUID read FGUID;
property Url : String read FUrl;
property ItemName : String read FItemName write FItemName;
property InitPrice : Currency read FInitPrice;
property InitDate : TDateTime read FInitDate;
property WantedPrice : Currency read FWantedPrice write FWantedPrice;
property LastPrice : Currency read FLastPrice;
property LastDate : TDateTime read FLastDate;
end;
TMonitorModel = class(TModelBase)
private
FItems : TObjectList<TMonitorItem>;
function GetCount : Integer;
function GetItem(Index : Integer) : TMonitorItem;
protected
procedure SaveToStream(AStream : TStream); override;
procedure LoadFromStream(AStream : TStream); override;
public
constructor Create;
destructor Destroy; override;
procedure GetGroupsList(var AList : TStringList); overload;
function GetGroupsList : TStringList; overload;
procedure Clear;
procedure AddItem(AItem : TMonitorItem);
procedure DeleteItem(Index : Integer);
procedure RemoveItem(AItem : TMonitorItem);
function IndexOf(AItem : TMonitorItem) : Integer;
function IndexOfByUrl(AUrl : String) : Integer;
class function GetModelFileName : String;
property Items[Index : Integer] : TMonitorItem read GetItem;
property Count : Integer read GetCount;
end;
implementation
uses
IdHTTP, IdSSLOpenSSL, MSHTML, SysFolders;
{ TMonitorItem }
procedure TMonitorItem.CreateInitFields;
begin
FGroupName := 'No group';
CoCreateGUID(FGUID);
FUrl := '';
FItemName := '';
FComments := '';
FInitPrice := 0.0;
FInitDate := 0;
FWantedPrice := 0.0;
FLastPrice := 0.0;
FLastDate := 0;
FOnHold := False;
FFlagged := False;
end;
procedure TMonitorItem.Assign(AItem : TMonitorItem);
begin
If Not Assigned(AItem)
Then Raise Exception.Create('TMonitorItem.Assign: AItem parameter is not assigned.');
Self.FGroupName := AItem.FGroupName;
Self.FGUID := AItem.FGUID;
Self.FUrl := AItem.FUrl;
Self.FItemName := AItem.FItemName;
Self.FComments := AItem.FComments;
Self.FInitPrice := AItem.FInitPrice;
Self.FInitDate := AItem.FInitDate;
Self.FWantedPrice := AItem.FWantedPrice;
Self.FLastPrice := AItem.FLastPrice;
Self.FLastDate := AItem.FLastDate;
Self.FOnHold := AItem.FOnHold;
Self.FFlagged := AItem.FFlagged;
end;
procedure TMonitorItem.UpdateData;
var
APage : String;
begin
// APage := ReadInternetPage(FUrl);
// SetValuesFromInternetPage(APage,False);
end;
constructor TMonitorItem.Create(AUrl : String);
var
APage : String;
begin
inherited Create;
CreateInitFields;
FUrl := AUrl;
APage := ReadInternetPage(FUrl);
SetValuesFromInternetPage(APage,True);
end;
constructor TMonitorItem.Load(AStream : TStream; ADataVer : Integer);
begin
inherited Create;
LoadFromStreamVer(AStream,ADataVer);
end;
procedure TMonitorItem.SaveToStream(AStream : TStream);
begin
WriteString(AStream,FGroupName);
WriteGUID(AStream,FGUID);
WriteString(AStream,FUrl);
WriteString(AStream,FItemName);
WriteString(AStream,FComments);
WriteCurrency(AStream,FInitPrice);
WriteDateTime(AStream,FInitDate);
WriteCurrency(AStream,FWantedPrice);
WriteCurrency(AStream,FLastPrice);
WriteDateTime(AStream,FLastDate);
WriteBool(AStream,FOnHold);
WriteBool(AStream,FFlagged);
end;
procedure TMonitorItem.LoadFromStream(AStream : TStream);
begin
LoadFromStreamVer(AStream,-1);
end;
procedure TMonitorItem.LoadFromStreamVer(AStream : TStream; ADataVer : Integer);
begin
If ADataVer = -1 Then ADataVer := sigFileVersion;
FGroupName := ReadString(AStream);
FGUID := ReadGUID(AStream);
FUrl := ReadString(AStream);
FItemName := ReadString(AStream);
FComments := ReadString(AStream);
FInitPrice := ReadCurrency(AStream);
FInitDate := ReadDateTime(AStream);
FWantedPrice := ReadCurrency(AStream);
FLastPrice := ReadCurrency(AStream);
FLastDate := ReadDateTime(AStream);
FOnHold := ReadBool(AStream);
FFlagged := ReadBool(AStream);
If ADataVer > 1 Then // Version 2 and hier
Begin
End;
end;
{ TMonitorModel }
function TMonitorModel.GetCount : Integer;
begin
Result := FItems.Count;
end;
function TMonitorModel.GetItem(Index : Integer) : TMonitorItem;
begin
If (Index < 0) Or (Index > FItems.Count) Then
Raise Exception.CreateFmt('GetItem. Index out of bounds (%d).',[Index]);
Result := FItems[Index];
end;
constructor TMonitorModel.Create;
begin
inherited;
FItems := TObjectList<TMonitorItem>.Create(True);
end;
destructor TMonitorModel.Destroy;
begin
FreeAndNil(FItems);
inherited;
end;
procedure TMonitorModel.SaveToStream(AStream : TStream);
var
I, ACnt : Integer;
begin
AStream.WriteBuffer(sigFileMark[1],Length(sigFileMark)*SizeOf(Char));
AStream.WriteBuffer(sigFileVersion,SizeOf(Integer));
ACnt := FItems.Count;
AStream.WriteBuffer(ACnt,SizeOf(Integer));
For I := 0 To ACnt-1 Do
Items[i].SaveToStream(AStream);
end;
procedure TMonitorModel.LoadFromStream(AStream : TStream);
var
I, ACnt : Integer;
AItem : TMonitorItem;
rdMark : String;
rdVersion : Integer;
begin
FItems.Clear;
SetLength(rdMark,Length(sigFileMark));
AStream.ReadBuffer(rdMark[1],Length(sigFileMark)*SizeOf(Char));
AStream.ReadBuffer(rdVersion,SizeOf(Integer));
If rdMark <> sigFileMark Then
Raise Exception.CreateFmt('Incorrect file signature (expected "%s", but got "%s").',[sigFileMark,rdMark]);
If rdVersion > sigFileVersion Then
Raise Exception.Createfmt('Wrong file version (expected less or equal %d, but found %d).',[sigFileVersion,rdVersion]);
AStream.ReadBuffer(ACnt,SizeOf(Integer));
For I := 0 To ACnt-1 Do
Begin
AItem := TMonitorItem.Load(AStream,rdVersion);
FItems.Add(AItem);
End;
end;
procedure TMonitorModel.GetGroupsList(var AList : TStringList);
var
I, J : Integer;
IsFound : Boolean;
begin
If Not Assigned(AList)
Then Raise Exception.Create('TMonitorModel.GetGroupsList: AList parameter is not assigned.');
AList.Clear;
For I := 0 To FItems.Count-1 Do
Begin
IsFound := False;
For J := 0 To AList.Count-1 Do
Begin
IsFound := AnsiCompareText((FItems[i] As TMonitorItem).FGroupName,AList[J]) = 0;
If IsFound Then Break;
End;
If Not IsFound
Then AList.Add((FItems[i] As TMonitorItem).FGroupName);
End;
end;
function TMonitorModel.GetGroupsList : TStringList;
begin
Result := TStringList.Create;
GetGroupsList(Result);
end;
procedure TMonitorModel.Clear;
begin
FItems.Clear;
end;
procedure TMonitorModel.AddItem(AItem : TMonitorItem);
begin
FItems.Add(AItem);
end;
procedure TMonitorModel.DeleteItem(Index : Integer);
begin
FItems.Delete(Index);
end;
procedure TMonitorModel.RemoveItem(AItem : TMonitorItem);
begin
FItems.Remove(AItem);
end;
function TMonitorModel.IndexOf(AItem : TMonitorItem) : Integer;
begin
Result := FItems.IndexOf(AItem);
end;
function TMonitorModel.IndexOfByUrl(AUrl : String) : Integer;
var
I : Integer;
begin
Result := -1;
For I := 0 To FItems.Count-1 Do
If (FItems[i] As TMonitorItem).Url = AUrl Then
Begin
Result := I;
Break;
End;
end;
class function TMonitorModel.GetModelFileName : String;
var
APath : String;
begin
Try
APath := GetUserAppDataFolderPath;
APath := IncludeTrailingPathDelimiter(APath + 'bhpvMonitor');
If Not DirectoryExists(APath) Then
If Not ForceDirectories(APath) Then
Raise Exception.Create('Can''t force user profile path.');
Except
APath := IncludeTrailingPathDelimiter(ExtractFilePath(Application.ExeName));
End;
Result := APath + 'items.dat';
end;
end.PS. Пришлось удалить пару методов, что бы влезть в ограничение длинны сообщения... |
| Этот пользователь сказал Спасибо lmikle за это полезное сообщение: | ||
Помидоркин (24.08.2022)
| ||
|
#6
|
||||
|
||||
|
Еще один вопрос возник, что происходит с данными при уменьшении длинны массива? Например запись
Код:
TPartOfAll = record
Part: TPart;
Total: Word;
Done: Word;
end;
TAllParts = array of TPartOfAll;
.........
SetLength(FAllParts, 0);Последний раз редактировалось Помидоркин, 22.09.2022 в 11:48. |
|
#7
|
|||
|
|||
|
как раз объект и надо уничтожать - иначе он останется "висеть" в куче и удалить его не будет возможности, т.к. ссылка на него будет потеряна.
А вот с "числами" как раз все все нормально будет, бо как это не указатели. Они будут удалены автоматически. Память будет помечена как свободная и может быть переиспользованна. При следующем выделении памяти компилятор просто зачистит выделенное место, т.е. старых данных ты уже не увидишь. |
| Этот пользователь сказал Спасибо lmikle за это полезное сообщение: | ||
Помидоркин (23.09.2022)
| ||
|
#8
|
||||
|
||||
|
Цитата:
Ну т.е. я так себе представляю - при задании длинны массива выделяется память (в моем случае) - (<сколько-то там под указатель>+2байта+2байта)*длинна массива, как-то так. |
|
#9
|
|||
|
|||
|
Ну, правильно представляеш в общем.
"<сколько-то там под указатель>" - это конкретно 32 или 64 бита (4 или 8 байт). Память под сам объект выделвется при его создании отдельно и к массиву отношения не имеет (это просто для уточнения). Ну и еще там есть место под длинну массива (если мы говорим о динамических массивах), но оно тебе на прямую не доступно (находится по отрицательному адресу относительно адреса первого элемента массива). Опять же, это просто для общего понимания. ЗЫ. Тут есть момент, связанный с оптимизацией. Для тебя он "прозрачен". А именно, как выделяется память при изменении размера массива. Если есть последовательно свободная память, то при увеличении размера массива она "прирезается" к уже выделенной. А вот ести нужного куска нет, то внутри происходит выделение полноразмерного куска в новом месте и копирование данных из старой в новую память. Поэтому для оптимизации рекомендуют либо заранее вычислить нужный размер массива и выделить память, либо выделять как минимум достаточно большими кусками. Кстати, именно так реализованы объекты-списки в VCL - они выделяют место кусками под несколько элементов и уже внутри себя контролируют использование. Последний раз редактировалось lmikle, 23.09.2022 в 19:50. |