Как стать автором
Обновить

Комментарии 34

Зачем так усложнять smart pointers?

Можно расшифровать, в чем усложнение?

Можете привести сценарий использования DeferredRef<T: class>?

Вот только сегодня столкнулся с интереснейшей ситуацией с дочерними процессами созданными через CreateProcess. Ситуация следующая, есть Win32 приложение, которое регулярно порождает дочерние, коротко живущие, процессы, все это дело завернуто в Job. И все это генерирует вполне заметную утечку памяти. Исследование показало, что при создании первого дочернего процесса создается еще и print driver host, в принципе это нормально, но оно начинает жрать память, которая не освобождается при завершение дочерних процессов. Т.е. каждый запуск дочернего процесса вызывает отжор памяти всего джоба, которая не возвращается. Краткое исследованные показало, что такое поведение проявляется только при обращении к функциям модуля Vcl.Printers. Я пока не разобрался, почему это вызывает утечку, баг это в нашей системе, или в системном модуле (он какой-то слегка странный), это еще предстоит выяснить, что не так с Printers и почему, даже при завершении процесса, spooler продолжает жрать память. Но, в реальной жизни, эти дочерние процессы, функции печати используют крайне редко. Завернув получение текущего принтера в Deferred я практически избавился от проблемы, конечно, в данной ситуации, это не 100% процентов корректное решение (с этим еще предстоит разбираться), но тем не менее...

Понятно что вы решали какую-то свою конкретную проблему. Но меня заинтересовал вопрос: как это использовать? Можно какой-нибудь привести демо-пример?

var Printer := DeferredRef(function: TPrinter begin Result := Vcl.Printers.Printer; end);

Хм... Спасибо

Парсер сожрал угловые скобки, но думаю смысл понятен. Буду учиться пользоваться местным редактором.

Местный редактор доступен только для элиты)

Теперь, вроде бы, встало на свои места.

class procedure Shared.Initialize<T>(var Value: T; const Initializer: AutoRef<T>);
begin
  if not Assigned(Value) then // вы уверены что это сработает?
  begin
..........................
..........................
..........................
  end;
end;

Если дженерик будет специфицирован в коде ссылочным типом T, то конечно сработает.

В общем случае нет, например для локальной переменной никто не гарантирует инициализацию в nil.
Но в данном коде все будет работать, так вызывается только для поля обьекта, а поля обьектов в Delphi гарантированно инициализируюся в nil при создании.
Но это некрасиво, этой проверке здесь в самом деле не место.

Согласен, проверка лишняя

вызывается только для поля объекта, а поля объектов в Delphi гарантированно инициализируются в nil при создании.

Особенность (на которую наверно ссылочку GunSmoker и давал) как раз и заключается в том, что инициализация для объектного поля записи в nil не гарантирована и эта особенность сохраняться начиная 2009 версии (с чем и столкнулся при своей реализации smartpounter-ов). Про самые последние версии сказать ничего не могу (но там уже есть М-записи, которые позволяют во многих случаях отказаться от smartpoiter-ов). Как  правило в объектном поле записи (record-а) сидит "мусор" -  Assigned(FValue) практически гарантировано может вернуть  true (может быть кто-нибудь сможет найти способ? Люди искали такой способ – на нашли). Об этом на протяжении по-меньшей мере лет 12 написано уже достаточно много (см. например блог  H. Vassbotn). Чтобы не наткнуться на этот «подводный камень» в DeferredRef<T: class>  необходимо проверять поле интерфейса  (его почему-то компилятор не забывает инициализировать nil-ом) – FCreator.

Похожее на описанное в данной статье решение, например, есть в статье Александра Багеля и многих др. Подобные «вещички» есть например в DeHL, DSharp и  Spring4D.

@GunSmoker давал ссылку на другую проблему, точнее особенность, которую есть смысл помнить. Выход за пределы блока, например begin/end, не гарантирует освобождение ссылки на интерфейс созданный внутри этого блока. В примере там внутри блока вызывается функция, которая возвращает интерфейс. И там автор надеялся, что после выхода из блока begin/end интерфейс точно освобожден. По факту никто этого не гарантирует и поведение может варьироваться в зависимости от компилятора или например настроек оптимизации. Гарантированно ссылка освободится только после выхода из процедуры/функции. Хотя даже эта гарантия так себе, если например функция инлайнится компилятором, то не исключено что освободится после выхода из внешней функции...

То что вы пишете о записях не противоречит тому что я написал ранее вроде бы.
В коде автора Initialize вызывается только в одном месте, только для поля FValue обьекта класса DeferredRef<T>.TDeferredRef и никогда для поля записи.

То есть вопрос сводится к тому, всегда ли экземляр класса TDeferredRef сконструирован при обращении к нему. Я такого сценария, когда он не сконструирован, не вижу. Хотя здесь легко что то упустить, приведенный код не отличается ясностью. А Вы видите такой сценарий?

...для поля FValue объекта класса DeferredRef<T>.TDeferredRef и никогда для поля записи...

Да это так.

А Вы видите такой сценарий?

Также как и вы - никакого. Я поначалу вообще рассматривал этот код как "классический" SP в том числе с точки зрения вариантов его использования. Сработал стереотип мышления.

Если говорить о DeferredRef<T>, то к моему глубокому сожалению я вообще не вижу какого-либо варианта его использования. Необходимости в чем-то подобном в моей практике не возникало. Может быть ap1973 приведет какой-либо пример?

В версии 10.4+ добавили возможность добавлять конструкторы и деструкторы для записей (record)

добавлять конструкторы и деструкторы для записей

Только ради полноты картины (прошу HemulGM не обижаться):

  1. конструкторы и деструкторы записей были введены в Delphi достаточно давно.

  2. У управляемых (менеджируемые, M-record-ы) записи, окончательно введенные в версии 10.4 и о которых я упомянул в своем комментарии, появилась возможность определять операторы инициализации (class operator Intialaze), финализации (class operator Finalize) и присваивания (class operator Assign). Собственно о них вы говорите. Так что функционал "классического" SmPt можно реализовать без использования техники SmPt нужным образом определяя Finalize и Assign. Конечно же у M-записей есть свои "рифы"...

Про деструкторы записей я погорячился...

Правда погорячились, и про конструкторы тоже. Они хоть и есть, но по факту это не конструкторы, т.к. записи в принципе не динамические объекты (не в куче они лежат).

Про SP на records, их у меня тоже есть, но как вы верно заметили, это только с версии 10.4.2 (по моему) есть Initialize, Finalize и Assign. Кроме того, по факту, там надо делать тот-же ARC, только руками.

Правда погорячились, и про конструкторы тоже...

Конструктор в том числе отвечает за инициализацию полей записей. Можно конечно использовать для этого class function. Не зря такие конструкторы обязаны иметь параметры. Но все же синтаксическая конструкция имеется... А class operator Initialize именно для этого и предназначен. Ничего другого от них и не требуется. В вашем понимании он также не будет полноценным конструктором.

Я с вами согласен, просто уточнил, что для записей constructor, это не тот конструктор, как обычно он понимается. А философские рассуждения является Initialize конструктором или нет - оставим философам, на мой взгляд смысл одинаков, ну может почти одинаков.

Можно тут продублировать? Что-то меня в QC не пускает (давно не заходил).

Можно здесь посмотреть: Delphi 10.4 / Delphi 11 Alexandria Breaking Changes.


Но в данном случае все нормально — ссылки на интерфейс явно хранятся в локальных переменных.

Для этой нужды я прeдлагал Embarcadero's Quality Portal вот такой синтакс.

using var Reader := TFileStream.Create('C:\hiberfil.sys', fmOpenRead) do

begin

  // Делаем что-то полезное

end; 

Это из мира C#. Удобно конечно, но синтаксис сомнительный, на мой взгляд.

А зачем так сложно, если можно так

with TFileStream.Create('C:\hiberfil.sys', fmOpenRead) do

begin

// Делаем что-то полезное

end;

В этом случае экземпляр класса TFileStream никогда не будет освобожден.
PS Не по теме статьи, но IMHO `With` вообще весьма сомнительная штука и его присутствие в современном языке сложно чем то оправдать. Внутри такого блока никогда нельзя быть уверенным, что именно используется, что то из текущей области видимости или что то из обьекта/записи для которой использован With.
И даже если в момент написания кода все корректно, достаточно добавить в класс новый член, и код использования этого класса/записи через with может перестать работать.

Полностью поддерживаю, видел комментарий выше, но только добрался до хабра. Я бы with уничтожил. Конечно поздно уже, но можно через директивы их угнать в obsolete.

конструкция using это синтаксический сахар для конструкции

var O := TFileStream.Create('C:\hiberfil.sys', fmOpenRead);
try
  // Делаем что-то полезное
finally
	O.Free;
end;

Тогда уж

With TFileStream.Create('C:\hiberfil.sys', fmOpenRead) do
try
  // Делаем что-то полезное
finally
	Free;
end;
Осталось добавить немного синтаксического сахара, с явным и не явным созданием нашего интерфейса

Я не против сахара и использую Delphi-7, но когда сахара много — бывает приторно.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории