2016-10-10 6 views
10

Delphi «создает экземпляр» каждого анонимного метода (например, объекта) ?, если так, когда Delphi создает этот экземпляр и, что самое главное, когда Delphi освобождает его?Как анонимные методы реализованы под капотом?

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

Каковы возможные недостатки объявления анонимного метода внутри другого анонимного метода. Возможны ли циркулярные ссылки?

+0

Смотрите также [Переменный Binding Механизм] (http://docwiki.embarcadero.com/RADStudio/о/Anonymous_Methods_in_Delphi # Variable_Binding_Mechanism). –

+0

См. Также: http://blog.barrkel.com/2008/07/anonymous-method-details.html –

+0

FWIW, Барри Келли реализовал анонимные методы. –

ответ

15

Анонимные методы реализованы как интерфейсы. В этой статье есть хорошее объяснение того, как это делается компилятором: Anonymous methods in Delphi: the internals.

По существу, сгенерированный компилятором интерфейс имеет один метод с именем Invoke, за которым вы предоставляете анонимный метод.

Захваченные переменные имеют одинаковую продолжительность жизни, как и любые анонимные методы, которые их захватывают. Анонимный метод - это интерфейс, и его время жизни управляется подсчетом ссылок. Следовательно, захваченные переменные жизни продлеваются до тех пор, пока анонимные методы их захватывают.

Как можно создавать круглые ссылки с интерфейсами, одинаково возможно создание круговых ссылок с использованием анонимных методов. Вот самая простая демонстрация того, что я могу построить:

uses 
    System.SysUtils; 

procedure Main; 
var 
    proc: TProc; 
begin 
    proc := 
    procedure 
    begin 
     if Assigned(proc) then 
     Beep; 
    end; 
end; 

begin 
    ReportMemoryLeaksOnShutdown := True; 
    Main; 
end. 

За кулисы компилятор создает скрытый класс, который реализует интерфейс анонимного метода. Этот класс содержит в качестве элементов данных любые перенесенные переменные. Когда назначается proc, что увеличивает счетчик ссылок на экземпляр реализации. Поскольку proc принадлежит экземпляру-реализации, этот экземпляр, таким образом, взял ссылку на себя.

Чтобы сделать это немного понятнее, эта программа представляет идентичную проблему, но переливается в терминах интерфейсов:

uses 
    System.SysUtils; 

type 
    ISetValue = interface 
    procedure SetValue(const Value: IInterface); 
    end; 

    TMyClass = class(TInterfacedObject, ISetValue) 
    FValue: IInterface; 
    procedure SetValue(const Value: IInterface); 
    end; 

procedure TMyClass.SetValue(const Value: IInterface); 
begin 
    FValue := Value; 
end; 

procedure Main; 
var 
    intf: ISetValue; 
begin 
    intf := TMyClass.Create; 
    intf.SetValue(intf); 
end; 

begin 
    ReportMemoryLeaksOnShutdown := True; 
    Main; 
end. 

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

procedure Main; 
var 
    proc: TProc; 
begin 
    proc := 
    procedure 
    begin 
     if Assigned(proc) then 
     Beep; 
    end; 
    proc := nil; 
end; 

эквивалент для варианта интерфейса является:

procedure Main; 
var 
    intf: ISetValue; 
begin 
    intf := TMyClass.Create; 
    intf.SetValue(intf); 
    intf.SetValue(nil); 
end; 
+0

метод 2, похоже, не имеет способа захвата 1 здесь –

+0

@ Arioch'The Для этого не требуется округлость и утечка памяти. –

+0

Да, я вижу последний абзац ответа Стефана, но ваш ответ не упоминает/не объясняет. Ваш ответ не объясняет, когда линейный справочный граф внезапно становится круговым. –

8

Анонимные методы реализованы как интерфейсы с помощью метода Invoke, который имеет ту же подпись, что и объявление анонимного метода. Таким образом, технически reference to function(a: Integer): string является бинарным совместимы с этим интерфейсом:

X = interface 
    function Invoke(a: Integer): string; 
end; 

До несколько версий назад можно было даже назвать .Invoke на анонимные методы, но компилятор в настоящее время не позволяет это.

Когда вы объявляете анонимный метод inline, компилятор создает некоторый код в прологе процедуры, чтобы гарантировать, что любая зафиксированная переменная не будет находиться в стеке, а в куче (это также является причиной, по которой вы не можете проверять любую захваченную переменную во время отладки, потому что, к сожалению, эта информация отсутствует). Компилятор создает класс за этим интерфейсом с полями, которые имеют то же имя, что и переменные, которые вы захватываете (см. this blog article для получения дополнительной информации).

Что касается круглых ссылок, то да. Имейте в виду, что когда вы, например, захватываете интерфейс (или объект в случае nextgen-платформ, где у вас включен ARC для объектов), вы можете вызвать круговую ссылку, вызывающую утечку памяти.

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

+0

Я думал, что захваченные переменные (locals, parameters, globals) были реализованы как поля (члены) интерфейса? Я знаю, что это был план, но я никогда не смотрел достаточно близко, если это действительно так. –

+2

@RudyVelthuis, с каких пор интерфейсы имеют поля? –

+0

Объектом реализации является реализация анонимного метода. Интерфейс используется только для управления жизненным циклом. Поскольку Барри Келли (который реализовал anonmeths) сказал: * «В принципе, переменная« x »(или любая другая переменная, затронутая анонимным методом) выгружается и превращается в поле класса. Экземпляр этого класса создается, когда функция вводится, а анонимные методы преобразуются в методы в этом скрытом классе ». * См. его комментарии здесь: https://www.reddit.com/r/programming/comments/6tp3i/pascal_gets_closures_before_java_why_hasnt_the/ –