Слабые[weak] ссылки в новой версии Delphi

от автора

Здравствуйте.

Компания Embarcadero вчера объявила о выходе новой версии Delphi RAD studio XE 10.1,
Весь список изменений можно посмотреть тут я же хочу рассказать о наиболее ценном(для нашей компании) улучшении, а именно о внедрение слабых [weak] ссылок в классический компилятор (Win32/Win64).

Выше в статье даны подробности проблемы, так что тем кто желает посмотреть что сделали в делфи прошу под кат.

Итак, рассмотрим в качестве примера использования слабых ссылок, интерфейсный контейнер. Конечно, слабые ссылки необходимы и в других ситуациях, но мы рассмотрим именно этот случай. В такой коллекции у нас есть следующая проблема: элементы контейнера должны иметь ссылку на коллекцию-владельца (например чтобы имея ссылку на элемент, его можно было удалить из коллекции или просто получить доступ к ней). В свою очередь сама коллекция также должна хранить ссылки на элементы. Так как в предыдущих версиях Делфи, у нас была возможность объявлять только обычные ссылки (strong references), то в результате мы получали циклические ссылки в данной коллекции, что приводило к невозможности корректно освободить память. Из данной ситуации выходили обычно двумя способами:
1. В классе элемента использовали не типизированный указатель для поля FOwner. Присвоение интерфейсной ссылки к такому указателю не приводило к увеличению счетчика ссылок, что решало проблему циклической ссылки, однако этот код не безопасен — в случае если коллекция умрет раньше элемента (ссылка на который где-то оказалась присвоена), то нет безопасного способа узнать об удалении коллекции — владельца. Указатель FOwner — продолжал указывать на уже удаленную память.
2. Чтобы решить проблему предыдущего способа можно внедрить оповещение элементов об удалении коллекции. При удалении коллекция пробегает по всем своим элементам и вызывает у них некий метод, который в свою очередь “заниливает” поле FOwner. Минус этого подхода — это дополнительный код и время необходимое на его выполнение. В случае с нашим примером коллекции это еще не так страшно, но в других не тривиальных примерах этот код может очень сильно запутать логику и добавить ошибок.
И вот в последней версии XE10.1 Berlin появилась по человечески нормальная возможность решать эту проблему. Все что нужно, это объявить поле FOwner c атрибутом [weak]. Такая ссылка по прежнему будет строго типизированной, но присвоение в которую не будет увеличивать счетчик ссылок на исходный объект. В случае же когда объект (в нашем случае коллекция-владелец) удаляется из памяти, такая ссылка автоматически “занилится”, что будет индикатором что объект удален.

Напишем два интерфейса, и 2 класса реализующих эти интерфейсы (данный пример упрощен до предела, а вопросы оптимизации скорости вообще не стояли)

Описание интерфейсов и классов

type   {интерфейс элемента}   IXListItem = interface     ['{02E680D6-9F86-4303-85B5-256ACD89AD46}']     function GetName: string;     procedure SetName(const Name: string);     property Name: string read GetName write SetName;   end;    {интерфейс коллекции}   IXList = interface     ['{922BDB26-4728-46DA-8632-C4F331C5A013}']     function Add: IXListItem;     function Count: Integer;     function GetItem(Index: Integer): IXListItem;     property Items[Index: Integer]: IXListItem read GetItem;     procedure Clear;   end;    {класс имплементатор элемента}   TXListItem = class(TInterfacedObject, IXListItem)   private     [weak] FOwner: IXList;  // слабая ссылка на коллекцию     FName: string;   public     constructor Create(const Owner: IXList);     destructor Destroy; override;     procedure SetName(const Name: string);     function GetName: string;   end;      {класс имплементатор коллекции}   TXList = class(TInterfacedObject, IXList)   private     FItems: array of IXListItem;   public     function Add: IXListItem;     function Count: Integer;     function GetItem(Index: Integer): IXListItem;     procedure Clear;     destructor Destroy; override;   end; 

Как видим в объекте TXListItem есть слабая ссылка на объект владелец.

Реализация класса TXListItem, в методе GetName мы пользуемся нашей слабой ссылкой:

TXListItem

{TXListItem} constructor TXListItem.Create(const Owner: IXList); begin   FOwner := Owner;  // получение слабой ссылки end;  destructor TXListItem.Destroy; begin   ShowMessage('Destroy ' + GetName);   inherited; end;  function TXListItem.GetName: string; var   List: IXList; begin   List := FOwner;    if Assigned(List) then     Exit(FName + '; owner assined!')   else     Exit(FName + '; owner NOT assined!'); end;  procedure TXListItem.SetName(const Name: string); begin   FName := Name; end; 

Класс TXList банален:

TXList

{ TXList }  function TXList.Add: IXListItem; var   c: Integer; begin   c := Length(FItems);   SetLength(FItems, c + 1);   Result := TXListItem.Create(Self);   FItems[c] := Result; end;  procedure TXList.Clear; var   i: Integer; begin   for i := 0 to Length(FItems) - 1 do     FItems[i] := nil; end;  function TXList.Count: Integer; begin   Result := Length(FItems); end;  destructor TXList.Destroy; begin   Clear;   ShowMessage('Destroy list');   inherited; end;  function TXList.GetItem(Index: Integer): IXListItem; begin   Result := FItems[Index]; end; 

Объявляем наши переменные:

var

var   Form1: TForm1;   List: IXList;   Item: IXListItem; 

И методы для создания и удаления объектов.

methods

procedure TForm1.btnListCreateAndFillClick(Sender: TObject); begin   List := TXList.Create;         // создаем коллекцию   List.Add.Name := 'item1';  // заполняем тремя элементами   List.Add.Name := 'item2';   Item := List.Add;  // последний элемент запоминаем в глобальную переменную   Item.Name := 'item3'; end;  procedure TForm1.btnListClearClick(Sender: TObject); begin   List := nil; // освобождаем коллекцию end;  procedure TForm1.btnLastItemFreeClick(Sender: TObject); begin   Item := nil;  // освобождаем последний элемент, который отдельно запомнили  end;  procedure TForm1.FormCreate(Sender: TObject); begin   ReportMemoryLeaksOnShutdown := true; // Включаем проверку утечек памяти end; 

Пример работы с объявлением [weak] ссылки:
image

Пример работы без объявлением [weak] ссылки:

Как видим без использования [weak] ссылки, коллекция и элементы циклически ссылаются друг на друга и у них не вызывается деструкторы, и соответственно мы получаем утечку памяти о чем нам и сообщает FastMM.

Также в новой версии появился атрибут [unsafe] который является аналогом [weak], но при удалении объекта на который она ссылается, ссылка автоматически не “заниливается”.
С ним кстати мы уже нашли один баг, который и отправили в qc.

Благодарю за пример и помощь в написании коллегу h_xandr.

ссылка на оригинал статьи https://habrahabr.ru/post/282035/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *