Здравствуйте.
Компания 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} 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 } 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 Form1: TForm1; List: IXList; Item: IXListItem;
И методы для создания и удаления объектов.
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] ссылки:
Пример работы без объявлением [weak] ссылки:
Как видим без использования [weak] ссылки, коллекция и элементы циклически ссылаются друг на друга и у них не вызывается деструкторы, и соответственно мы получаем утечку памяти о чем нам и сообщает FastMM.
Также в новой версии появился атрибут [unsafe] который является аналогом [weak], но при удалении объекта на который она ссылается, ссылка автоматически не “заниливается”.
С ним кстати мы уже нашли один баг, который и отправили в qc.
Благодарю за пример и помощь в написании коллегу h_xandr.
ссылка на оригинал статьи https://habrahabr.ru/post/282035/
Добавить комментарий