|
|
Регистрация | << Правила форума >> | 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. |