嵌套闭包和捕获变量

Nested closures and captured variables

我有这个带有嵌套闭包的示例,它演示了内存泄漏

use v5.10;
use strict;

package Awesome;

sub new {
    bless {steps => [], surprise => undef}, shift;
}

sub say {
    print "awesome: ", $_[1], "\n";
}

sub prepare {
    my ($self, @steps) = @_;

    for my $s (@steps) {
        push @{$self->{steps}}, sub {
            $self->say($s);

            if ($s eq 'pony') {
                $self->{surprise} = sub {
                    $s;
                }
            }
        };
    }
}

sub make {
    my $self = shift;

    while (my $step = shift @{$self->{steps}}) {
        $step->();
    }

    if ($self->{surprise}) {
        printf("And you have surprise: %s\n", $self->{surprise}->());
    }
}

sub DESTROY {
    warn "destroying";
}

package main;

my $a = Awesome->new;
$a->prepare('barbie','pony','flash');
$a->make();

我的 perl 的输出是

awesome: barbie
awesome: pony
awesome: flash
And you have surprise: pony
destroying at /tmp/t.pl line 43 during global destruction.

而这个"during global destruction"意味着这个对象不能以正常的方式销毁,因为它有一些循环引用。

然而,唯一的循环引用是由

创建的
push @{$self->{steps}}, sub {
            $self->say($s);

我们在第一个闭包中使用 $self。稍后在 make() 中,我们将删除这些步骤和循环引用。但是看起来这个带有 "suprise" 的嵌套闭包会产生问题。例如,如果我们不将 "pony" 传递给 prepare() 输出将如预期的那样好:

awesome: barbie
awesome: flash
destroying at /tmp/t.pl line 43.

那么,即使我们不使用它们,perl 中的嵌套闭包是否捕获了与已经捕获的上层闭包相同的变量?

Perl 以前在嵌套闭包中过度捕获,但从 5.18 开始不再这样做。

$ tail -n 9 a.pl   # Modified to make clearer when the object is destroyed.
package main;

{
   my $a = Awesome->new;
   $a->prepare('barbie','pony','flash');
   $a->make();
}

print "done.\n";

$ 5.16.3t/bin/perl a.pl
awesome: barbie
awesome: pony
awesome: flash
And you have surprise: pony
done.
destroying at a.pl line 43 during global destruction.

$ 5.18.2t/bin/perl a.pl
awesome: barbie
awesome: pony
awesome: flash
And you have surprise: pony
destroying at a.pl line 43.
done.