"Слабая ссылка": требуется объяснение до земли

Может ли кто-нибудь объяснить объяснение слабой ссылки в Delphi?

Я заметил, что эта концепция часто упоминается в некотором исходном коде библиотеки/структуры, который я тщательно изучаю. Я нахожусь в неопределенности и хочу иметь четкое понимание этого.

Ответы

Ответ 1

Экземпляры, которые ссылаются друг на друга по ссылкам интерфейса, поддерживают друг друга в реализации интерфейса на основе ссылок.

Слабая ссылка используется для того, чтобы сломать "держать друг друга живым". Это делается путем объявления одной ссылки как чистого указателя для обхода механизма подсчета ссылок.

IFriend = Interface(IInterface)
end;

TFriend = class(TInterfacedObject, IFriend)
private
  FFriend: IFriend;
end;


var
  Peter: IFriend;
  John: IFriend;
begin
  Peter := TFriend.Create;
  John := TFriend.Create;

  Peter.Friend := John;
  John.Friend := Peter;
end;

Даже когда Питер и Джон выходят из сферы действия, их экземпляры хранятся вокруг, потому что их взаимная ссылка заставляет их refcount отбрасываться до нуля.

Проблема чаще встречается в составных шаблонах (отношения между родителями и дочерними элементами), где у ребенка есть обратная ссылка на родителя:

ISomething = Interface(IInterface)
end;

TSomething = class(TInterfacedObject, ISomething)
end;

TParent = class(TSomething)
  FChildren: TInterfacedList;
end;

TChild = class(TSomething)
  FParent: ISomething;
end;

Опять же, родительский и дочерний могут поддерживать друг друга, потому что их взаимная ссылка не позволяет их refcount отбрасываться до нуля.

Это решается с помощью weak reference:

TChild = class(TSomething)
  FParent: Pointer;
end;

Объявляя FParent как "чистый" указатель, механизм подсчета ссылок не входит в игру для обратной ссылки на родителя. Когда родитель выходит из сферы действия, его счетчик ссылок теперь может упасть до нуля, потому что его дочерние элементы больше не сохраняют свое значение ref выше нуля.

Примечание Это решение требует пристального внимания к управлению жизненным циклом. Дети могут оставаться в живых вне времени жизни родителя, когда что-то на "вне" этих классов содержит ссылку на ребенка. И это может привести ко всевозможным интересным AV, когда ребенок принимает родительскую ссылку, всегда указывает на действительный экземпляр. Если вам это нужно, убедитесь, что, когда родитель выходит из сферы действия, он заставляет дочерние элементы ссылаться на свои обратные ссылки, прежде чем он обратит свои собственные ссылки на его дочерние элементы.

Ответ 2

По умолчанию в Delphi все ссылки:

  • слабые ссылки для экземпляров pointer и class;
  • Явная копия для низкоуровневых типов значений типа integer, Int64, currency, double или record (и старых устаревших object или shortstring);
  • copy-on-write с подсчет ссылок для высокопроизводительных вычислений, (например, string, widestring, variant или динамический массив);
  • сильная ссылка с подсчетом ссылок для экземпляров interface;

Основная проблема с сильным подсчетом ссылок - потенциальная проблема с круговой ссылкой. Это происходит, когда interface имеет сильную ссылку на другую, но цель interface имеет сильный указатель назад к оригиналу. Даже когда все другие ссылки удаляются, они все равно будут держаться друг за друга и не будут выпущены. Это также может происходить косвенно, цепочкой объектов, которые могут иметь последнее в цепочке, ссылаясь на более ранний объект.

См., например, следующее определение интерфейса:

  IParent = interface
    procedure SetChild(const Value: IChild);
    function GetChild: IChild;
    function HasChild: boolean;
    property Child: IChild read GetChild write SetChild;
  end;

  IChild = interface
    procedure SetParent(const Value: IParent);
    function GetParent: IParent;
    property Parent: IParent read GetParent write SetParent;
  end;

Следующая реализация будет окончательно утечка памяти:

procedure TParent.SetChild(const Value: IChild);
begin
  FChild := Value;
end;

procedure TChild.SetParent(const Value: IParent);
begin
  FParent := Value;
end;

В Delphi наиболее распространенный вид переменных-копий-копий (то есть вариант, динамический массив или строка) решает эту проблему путем реализации copy-on-write. К сожалению, этот шаблон не применим к интерфейсу, который не является объектами ценности, а ссылочными объектами, привязанными к классу реализации, которые нельзя скопировать.

Обратите внимание, что эти мусорные сборщики (например, Java или С#) не страдают от этой проблемы, так как циклические ссылки обрабатываются по их модели памяти: время жизни объектов поддерживается глобально менеджером памяти. Разумеется, это увеличит использование памяти, замедлит процесс из-за дополнительных действий при распределении и назначениях (все объекты и их ссылки должны поддерживаться во внутренних списках) и может замедлить работу приложения, когда сборщик мусора входит в действие.

Одним из распространенных решений с языками без сбора мусора (например, Delphi) является использование слабых указателей, которым интерфейс присваивается свойству без увеличения количества ссылок. Чтобы легко создать слабый указатель, можно использовать следующую функцию:

procedure SetWeak(aInterfaceField: PIInterface; const aValue: IInterface);
begin
  PPointer(aInterfaceField)^ := Pointer(aValue);
end;

Следовательно, он может быть использован как таковой:

procedure TParent.SetChild(const Value: IChild);
begin
  SetWeak(@FChild,Value);
end;

procedure TChild.SetParent(const Value: IParent);
begin
  SetWeak(@FParent,Value);
end;

Вы можете прочитать мое сообщение в блоге о слабых ссылках в Delphi - и связанный с ним исходный код: мы внедрили прямую слабую ссылку и "обнуление" обработки слабых опорных интерфейсов от Delphi 6 до XE2.

Фактически, в некоторых случаях вам нужно установить слабые поля интерфейса в nil, если вы освободите экземпляр ссылки до его дочернего элемента, чтобы избежать проблемы с доступом. Это называется " Нулевые слабые указатели", и что Apple реализовано с помощью модели ARC, и что мы пытались реализовать в Delphi.

Ответ 4

В наиболее общем случае a strong reference управляет временем жизни ссылочного экземпляра, а weak reference - нет. Термин weak reference может использоваться в контексте сборщика мусора, ссылок на подсчитанные интерфейсы или общих объектов.

Например, форма Delphi содержит ссылки на все ее элементы управления; эти ссылки можно назвать сильными, потому что, когда форма уничтожается, ее элементы управления также уничтожаются. С другой стороны, элемент управления Delphi имеет ссылку на форму, к которой он принадлежит. Эта ссылка может быть слабой, потому что она никак не управляет временем жизни формы.