В настоящее время я немного застрял в отношении объектов и интерфейсов и их управления памятью. У меня есть класс, который наследуется от TInterfacedPersistent
, называемый TAbstractBaseDTO
. У меня также есть интерфейс IDuplicatable
с функцией function CreateDuplicate: TAbstractBaseDTO
.
Я использую интерфейсы для достижения абстракции, а не для управления памятью, и именно поэтому я использую TInterfacedPersistent
как класс-предок.
ед. пас
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, System.Actions, Vcl.ActnList,
Vcl.StdCtrls;
type
TAbstractBaseDTO = class abstract (TInterfacedPersistent)
public
constructor CreateEmpty; virtual; abstract;
end;
IDuplicatable = interface
['{153275DC-71C0-4CB6-B933-667419950C68}']
function CreateDuplicate: TAbstractBaseDTO;
end;
TCountryMasterDataDTO = class (TAbstractBaseDTO, IDuplicatable)
public
constructor CreateEmpty; override;
function CreateDuplicate: TAbstractBaseDTO;
end;
TBaseDataForm = class(TForm)
ActionList1: TActionList;
actDuplicate: TAction;
Button1: TButton;
procedure actDuplicateExecute(Sender: TObject);
strict private
var
FDataObject: TAbstractBaseDTO;
function FetchBusinessObject: TAbstractBaseDTO;
public
procedure LoadData(ADataObject: TAbstractBaseDTO);
destructor Destroy; override;
end;
var
BaseDataForm: TBaseDataForm;
implementation
{$R *.dfm}
procedure TBaseDataForm.LoadData(ADataObject: TAbstractBaseDTO);
begin
FDataObject.Free;
FDataObject := ADataObject;
end;
destructor TBaseDataForm.Destroy;
begin
FDataObject.Free;
inherited;
end;
constructor TCountryMasterDataDTO.CreateEmpty;
begin
Create;
end;
function TCountryMasterDataDTO.CreateDuplicate: TAbstractBaseDTO;
begin
Result := TCountryMasterDataDTO.Create;
end;
procedure TBaseDataForm.actDuplicateExecute(Sender: TObject);
var
LAbstractBaseDTO: IDuplicatable;
LAbstractBaseDTODuplicate: TAbstractBaseDTO;
begin
LAbstractBaseDTO := Self.FetchBusinessObject as IDuplicatable;
try
LAbstractBaseDTODuplicate := LAbstractBaseDTO.CreateDuplicate;
Self.LoadData(LAbstractBaseDTODuplicate);
finally
LAbstractBaseDTO := nil;
end;
end;
function TBaseDataForm.FetchBusinessObject: TAbstractBaseDTO;
begin
Result := TCountryMasterDataDTO.Create;
end;
end.
Когда я запускаю действие actDuplicate
, FastMM4 сообщает об утечке памяти при завершении работы.
Однако изменение функции выполнения на:
procedure TBaseDataForm.actDuplicateExecute(Sender: TObject);
var
LAbstractBaseDTO: TAbstractBaseDTO;
LAbstractBaseDTODuplicate: TAbstractBaseDTO;
begin
LAbstractBaseDTO := Self.FetchBusinessObject;
try
LAbstractBaseDTODuplicate := (LAbstractBaseDTO as IDuplicatable).CreateDuplicate;
Self.LoadData(LAbstractBaseDTODuplicate);
finally
LAbstractBaseDTO.Free;
end;
end;
немедленно вызовет нарушение прав доступа при запуске действия.
Как я могу решить эту проблему?
Да, результат от FetchBusinessObject
нужно где-то освобождать. На самом деле LAbstractBaseDTODuplicate
будет загружен в форму для заполнения FDMemTable
. Также эта форма освободит текущий объект и заменит его на LAbstractBaseDTODuplicate
. Я постараюсь добавить только соответствующий код, так как все это происходит в нескольких формах. Я также изменил метод интерфейса, чтобы он возвращал IDuplicatable
вместо TAbstractBaseDTO
.
Единственная веская причина когда-либо получать от TInterfacedPersistent
— включить interface
подсчет ссылок. Это означает, что любой созданный вами объект, производный от TInterfacedPersistent
, должен использоваться только через interface
ссылки, а не object
ссылки. Если вам нужны ссылки на объекты, не используйте TInterfacedPersistent
, вместо этого используйте TPersistent
и вручную Free
объекты, когда это необходимо.
Если на то пошло, мне интересно, почему вы вообще используете T(Interfaced)Persistent
? Эти базовые классы предназначены для включения RTTI для классов, например для потоковой передачи DFM, которые не используются в показанном коде (т. е. без свойств published
).
Хорошо спасибо! На самом деле я видел использование интерфейса, больше похожего на контейнер с определенным поведением («может быть продублировано»), чем на самом деле позволить delphi управлять памятью для меня. Я понял, что если я перемещу весь контент из actDuplicateExecute
в конкретную форму и просто вызову его из базовой формы, это сработает. Не совсем уверен, где что-то не так с бросками. Создав минимальный воспроизводимый пример, ошибки к сожалению тоже не возникло. Так что, наверное, я что-то напутал с гипсом. В любом случае, теперь это работает, и спасибо Дэвиду и Реми.
Основная проблема здесь заключается в том, что вы смешиваете ссылки на интерфейсы со ссылками на объекты. Ваш код немного запутан, поэтому трудно сказать, что именно там не так. Вы должны создать правильный минимальный воспроизводимый пример, если хотите получить более конкретные ответы. TInterfacedPersistent
класс имеет отключенный подсчет ссылок, и вам нужно вручную управлять его памятью. Однако в тот момент, когда вы освобождаете такой объект, вы должны убедиться, что нет интерфейсов, указывающих на него, потому что, если они есть, у вас будет AV, когда эта ссылка выйдет за пределы области видимости, и метод интерфейса _Release
будет вызываться в мертвом экземпляре.
Если вы хотите задействовать интерфейсы, то было бы проще оставить управление памятью интерфейсам и использовать TInterfacedObject
в качестве базового класса, поскольку проектирование иерархий классов вокруг классов с отключенным подсчетом ссылок требует немного больше знаний (из-за более тонких проблем, которые могут возникнуть с приведением и хранение ссылок), что с использованием подсчета ссылок.
Но, глядя на ваши классы и тот факт, что у вас есть базовый класс, зачем вообще использовать интерфейс? Просто добавьте виртуальную функцию CreateDuplicate
в базовый класс и все. Если некоторые из классов-потомков не могут быть продублированы, вы можете либо вернуть nil из CreateDuplicate
как указание на то, что объект не может быть продублирован, либо добавить другую виртуальную функцию IsDuplicatable: Boolean
, которая сообщит вам, поддерживает ли конкретный класс-потомок дублирование или нет.
Спасибо, Далия! Я пытался добиться именно этого поведения (дублируемого или нет) с помощью интерфейса, чтобы реализовать только функцию, в которой реализован интерфейс, и позволить функции Support
обрабатывать все проверки для меня :). Как я уже говорил, я могу сам управлять памятью (освобождая объекты). Это не причина, по которой я хотел использовать интерфейс здесь. Это больше похоже на описание того, что могут делать мои классы.
Я понимаю, что вы хотели использовать интерфейсы как инструмент абстракции. Однако их управление памятью всегда сложнее, чем использование простых классов без интерфейсов.
Опять же, если вы хотите получить ответы на свой вопрос, вам нужно немного сократить приведенный выше код и удалить все лишнее. Вам не нужно полное объявление объекта со всеми полями. Также вы разместили два объявления интерфейса IDuplicatable
, одно из которых возвращает тип IDuplicatable
, а другое возвращает тип TAbstractBaseDTO
. Какой из них находится в вашем реальном коде? Это может радикально изменить то, что происходит в вашем коде.
Кроме того, у вас слишком много ненужного приведения типов. из вашего кода кажется, что вы хотите вернуть TAbstractBaseDTO
из CreateDuplicate
, а не IDuplicatable
, поскольку, как только у вас есть дубликат, вы хотите использовать этот экземпляр для других целей, а не вызывать CreateDuplicate
снова, поэтому нет смысла возвращать IDuplicatable
. Далее, зачем вообще нужно создавать дубликаты? Поскольку вы освобождаете LAbstractBaseDTO
, который возвращается FetchBusinessObject
, почему бы вам не передать этот объект LoadData
напрямую?
Еще раз спасибо @DalijaPrasnikar. Изначально я просто хочу вернуть TAbstractBaseDTO
от CreateDuplicate
. Извините, что не указал, метод CreateDuplicate
не возвращает точную копию объекта, а копирует одни значения и сбрасывает другие. Но, возможно, важно отметить, что это новый объект, который нужно освободить. Поэтому я хочу, чтобы эта логика в классе была своего рода бизнес-логикой, и загружала этот новый объект в метод LoadData
, а не только старый. Вы правы, мне не нужен результат как IDuplicatable
, поэтому я изменил его обратно на возвращаемый тип TAbstractBaseDTO
.
Проблема, с которой вы столкнулись, заключается в том, что существует живая ссылка интерфейса на объект, который вы уже уничтожили.
В данном случае это скрытая неявная ссылка на интерфейс, созданная компилятором при преобразовании типа LAbstractBaseDTO
как IDuplicatable
.
LAbstractBaseDTODuplicate := (LAbstractBaseDTO as IDuplicatable).CreateDuplicate;
Эта скрытая ссылка на интерфейс будет очищена компилятором в эпилоге метода, и это вызовет метод _Release
для уже уничтоженного экземпляра объекта.
Если вы запустите свой код с FASTMM в режиме полной отладки, FASTMM обнаружит такой сценарий и покажет сообщение об ошибке, за которым следует трассировка стека:
FASTMM обнаружил попытку использования интерфейса освобожденного объекта. Теперь будет выдано нарушение прав доступа, чтобы прервать текущую операцию.
Чтобы решить эту проблему, вам нужно преобразовать эту неявную ссылку на интерфейс в явную под вашим контролем, а затем вы можете очистить эту ссылку самостоятельно, прежде чем освободить объект, на который она ссылается.
procedure TBaseDataForm.actDuplicateExecute(Sender: TObject);
var
LAbstractBaseDTO: TAbstractBaseDTO;
LAbstractBaseDTODuplicate: TAbstractBaseDTO;
LDuplicatable: IDuplicatable;
begin
LAbstractBaseDTO := FetchBusinessObject;
try
// save interface reference to a variable
LDuplicatable := LAbstractBaseDTO as IDuplicatable;
LAbstractBaseDTODuplicate := LDuplicatable.CreateDuplicate;
LoadData(LAbstractBaseDTODuplicate);
finally
LDuplicatable := nil;
LAbstractBaseDTO.Free;
end;
end;
Создает ли
FetchBusinessObject
новый объект, который нужно освободить? ПочемуCreateDuplicate
возвращает ссылку на объект вместо интерфейса? КакLAbstractBaseDTODuplicate
используется после создания? Пожалуйста, предоставьте минимальный воспроизводимый пример