Здравствуйте.
Компания Embarcadero вчера объявила о выходе новой версии Delphi RAD studio XE 10.1,
Весь список изменений можно посмотреть тут, я же хочу рассказать о наиболее ценном(для нашей компании) улучшении, а именно о внедрение слабых [weak] ссылок в классический компилятор (Win32/Win64).
Выше в статье даны подробности проблемы, так что тем кто желает посмотреть что сделали в делфи прошу под кат.
Итак, рассмотрим в качестве примера использования слабых ссылок, интерфейсный контейнер. Конечно, слабые ссылки необходимы и в других ситуациях, но мы рассмотрим именно этот случай. В такой коллекции у нас есть следующая проблема: элементы контейнера должны иметь ссылку на коллекцию-владельца (например чтобы имея ссылку на элемент, его можно было удалить из коллекции или просто получить доступ к ней). В свою очередь сама коллекция также должна хранить ссылки на элементы. Так как в предыдущих версиях Делфи, у нас была возможность объявлять только обычные ссылки (strong references), то в результате мы получали циклические ссылки в данной коллекции, что приводило к невозможности корректно освободить память. Из данной ситуации выходили обычно двумя способами:
1. В классе элемента использовали не типизированный указатель для поля FOwner. Присвоение интерфейсной ссылки к такому указателю не приводило к увеличению счетчика ссылок, что решало проблему циклической ссылки, однако этот код не безопасен — в случае если коллекция умрет раньше элемента (ссылка на который где-то оказалась присвоена), то нет безопасного способа узнать об удалении коллекции — владельца. Указатель FOwner — продолжал указывать на уже удаленную память.
2. Чтобы решить проблему предыдущего способа можно внедрить оповещение элементов об удалении коллекции. При удалении коллекция пробегает по всем своим элементам и вызывает у них некий метод, который в свою очередь “заниливает” поле FOwner. Минус этого подхода — это дополнительный код и время необходимое на его выполнение. В случае с нашим примером коллекции это еще не так страшно, но в других не тривиальных примерах этот код может очень сильно запутать логику и добавить ошибок.
И вот в последней версии XE10.1 Berlin появилась по человечески нормальная возможность решать эту проблему. Все что нужно, это объявить поле FOwner c атрибутом [weak]. Такая ссылка по прежнему будет строго типизированной, но присвоение в которую не будет увеличивать счетчик ссылок на исходный объект. В случае же когда объект (в нашем случае коллекция-владелец) удаляется из памяти, такая ссылка автоматически “занилится”, что будет индикатором что объект удален.
Напишем два интерфейса, и 2 класса реализующих эти интерфейсы (данный пример упрощен до предела, а вопросы оптимизации скорости вообще не стояли)
Как видим в объекте TXListItem есть слабая ссылка на объект владелец.
Реализация класса TXListItem, в методе GetName мы пользуемся нашей слабой ссылкой:
Класс TXList банален:
Объявляем наши переменные:
И методы для создания и удаления объектов.
Пример работы с объявлением [weak] ссылки:
Пример работы без объявлением [weak] ссылки:
Как видим без использования [weak] ссылки, коллекция и элементы циклически ссылаются друг на друга и у них не вызывается деструкторы, и соответственно мы получаем утечку памяти о чем нам и сообщает FastMM.
Также в новой версии появился атрибут [unsafe] который является аналогом [weak], но при удалении объекта на который она ссылается, ссылка автоматически не “заниливается”.
С ним кстати мы уже нашли один баг, который и отправили в qc.
Благодарю за пример и помощь в написании коллегу h_xandr.
upd. Ссылка на реппозиторий
Компания 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] ссылки:
Пример работы без объявлением [weak] ссылки:
Как видим без использования [weak] ссылки, коллекция и элементы циклически ссылаются друг на друга и у них не вызывается деструкторы, и соответственно мы получаем утечку памяти о чем нам и сообщает FastMM.
Также в новой версии появился атрибут [unsafe] который является аналогом [weak], но при удалении объекта на который она ссылается, ссылка автоматически не “заниливается”.
С ним кстати мы уже нашли один баг, который и отправили в qc.
Благодарю за пример и помощь в написании коллегу h_xandr.
upd. Ссылка на реппозиторий